From: Stuart Prescott Date: Fri, 4 Sep 2026 05:00:44 +0000 (+1000) Subject: New upstream version 6.11.2 X-Git-Tag: archive/raspbian/6.11.2-2+rpi1^2~13^2 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=92e34180fa60230d67c57177f24c1cb10fd4358f;p=pyside6.git New upstream version 6.11.2 --- diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8429d3aa..00000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: python -dist: trusty -python: - - "2.7" - - "3.5" -before_install: - - sudo add-apt-repository ppa:beineri/opt-qt551-trusty -y - - sudo apt-get update -install: - - sudo apt-get install qt55-meta-full -y -script: - - source /opt/qt55/bin/qt55-env.sh - - python setup.py install --jobs=2 --build-tests # --openssl=/path/to/openssl/bin - # how do we separate the build step and the tests, to see both as a result? diff --git a/CMakeLists.txt b/CMakeLists.txt index f61a4f25..d9f8dc50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.18) -cmake_policy(VERSION 3.18) +cmake_minimum_required(VERSION 3.22) +cmake_policy(VERSION 3.22) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build Type") @@ -15,11 +15,12 @@ endif() # Used to prevent overriding message function in both shiboken6 and pyside6. set(is_pyside6_superproject_build 1) +add_subdirectory(sources/shiboken6_generator) add_subdirectory(sources/shiboken6) # Ensure Shiboken6 and Shiboken6Tools packags are found during super-build list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_BINARY_DIR}/sources/shiboken6/data") -list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_BINARY_DIR}/sources/shiboken6/generator") +list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_BINARY_DIR}/sources/shiboken6_generator/generator") add_subdirectory(sources/pyside6) add_subdirectory(sources/pyside-tools) diff --git a/README.md b/README.md index 4d8cc05b..1c9a1b2c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Qt For Python -Qt For Python is the [Python Qt bindings project](https://wiki.qt.io/PySide6), +Qt For Python is the [Python Qt bindings project](https://wiki.qt.io/Qt_for_Python), providing access to the complete Qt framework as well as to generator tools for rapidly generating bindings for any C++ libraries. @@ -118,7 +118,7 @@ using `setup.py build`: ## Requirements - * Python 3.9+ is supported (for Qt 6.10+) + * Python 3.10+ is supported (for Qt 6.11+) * CMake: Specify the path to cmake with `--cmake` option or add cmake to the system path. * Qt 6.x is supported. Specify the path to qtpaths with `--qtpaths` option or @@ -226,5 +226,5 @@ The invocation would then look like: ```bash mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release .. -# make or nmake or msbuild or jom +# ninja ``` diff --git a/README.pyside6.md b/README.pyside6.md index 39dbf9da..e61a5acd 100644 --- a/README.pyside6.md +++ b/README.pyside6.md @@ -15,8 +15,7 @@ design process. We welcome any contribution conforming to the ### Installation -Since the release of the [Technical Preview](https://blog.qt.io/blog/2018/06/13/qt-python-5-11-released/) -it is possible to install via `pip`, both from Qt's servers +It is possible to install via `pip`, both from Qt's servers and [PyPi](https://pypi.org/project/PySide6/): ``` @@ -31,29 +30,29 @@ pip install PySide6 #### Dependencies PySide6 versions following 6.0 use a C++ parser based on -[Clang](http://clang.org/). The Clang library (C-bindings), version 13.0 or -higher is required for building. Prebuilt versions of it can be downloaded from +[Clang](https://clang.llvm.org/). The Clang library (C-bindings), +is required for building. Prebuilt versions of it can be downloaded from [download.qt.io](https://download.qt.io/development_releases/prebuilt/libclang/). After unpacking the archive, set the environment variable *LLVM_INSTALL_DIR* to point to the folder containing the *include* and *lib* directories of Clang: ``` -7z x .../libclang-release_100-linux-Rhel7.2-gcc5.3-x86_64-clazy.7z +7z x .../libclang-release_22.1.8-based-linux-Rhel9.6-gcc11.4-x86_64.7z export LLVM_INSTALL_DIR=$PWD/libclang ``` On Windows: ``` -7z x .../libclang-release_100-windows-vs2015_64-clazy.7z +7z x .../libclang-release_22.1.8-based-windows-vs2022_64.7z SET LLVM_INSTALL_DIR=%CD%\libclang ``` ### Building from source For building PySide6 from scratch, please read about -[getting started](https://doc.qt.io/qtforpython/gettingstarted.html). +[Building from Source](https://doc.qt.io/qtforpython-6/building_from_source/index.html). This process will include getting the code: ``` @@ -66,11 +65,11 @@ then install the dependencies, and following the instructions per platform. A common build command will look like: ``` -python setup.py install --qtpaths=/path/to/bin/qtpaths6 --parallel=8 --build-tests +python setup.py install --qtpaths=/path/to/bin/qtpaths6 --build-tests ``` You can obtain more information about the options to build PySide and Shiboken -in [our wiki](https://wiki.qt.io/Qt_for_Python/). +in [our wiki](https://wiki.qt.io/Qt_for_Python). ### Documentation and Bugs diff --git a/README.pyside6_addons.md b/README.pyside6_addons.md index 9121d661..f3f8a071 100644 --- a/README.pyside6_addons.md +++ b/README.pyside6_addons.md @@ -20,6 +20,7 @@ it includes the following Qt modules: * Qt3DRender * QtAxContainer * QtBluetooth +* QtCanvasPainter * QtCharts * QtDataVisualization * QtGraphs diff --git a/README.shiboken6-generator.md b/README.shiboken6-generator.md index 11e345f1..7f486a5b 100644 --- a/README.shiboken6-generator.md +++ b/README.shiboken6-generator.md @@ -19,16 +19,16 @@ data types, manipulate the ownership of the objects, etc. # Examples -An example related to wrap a C++ library not depending on Qt can be found in -our [repository](https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples/samplebinding). +For an example related to wrap a C++ library not depending on Qt see the +[Sample Bindings Example](https://doc.qt.io/qtforpython-6/examples/example_samplebinding_samplebinding.html). Additionally, you can find a couple of tests inside the [git repository](https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/shiboken6/tests). For a more advanced case regarding extending a Qt/C++ application with Python bindings based on the idea of the PySide module, you can check the -[scriptableapplication](https://code.qt.io/cgit/pyside/pyside-setup.git/tree/examples/scriptableapplication) -example in our repository. +[Scriptable Application Example](https://doc.qt.io/qtforpython-6/examples/example_scriptableapplication_scriptableapplication.html) +example. # Documentation diff --git a/build_history/blacklist.txt b/build_history/blacklist.txt index b2aaecb8..4fc5ed66 100644 --- a/build_history/blacklist.txt +++ b/build_history/blacklist.txt @@ -1,11 +1,3 @@ -[smart::smart_pointer] - darwin ci -[testmodifydocumentation] - darwin arm -[signals::signal_signature_test] - linux - darwin - win32 [Qt3DExtras::qt3dextras_test] win32 ci linux @@ -13,46 +5,16 @@ # Cannot create metal surface [QtMultimediaWidgets::qmultimediawidgets] darwin ci -# PYSIDE-474 -[QtWebEngineWidgets::pyside-474-qtwebengineview] - py3 -[QtCore::qthread_test] -# PYSIDE-754 -[QtWebEngineCore::web_engine_custom_scheme] - win32 - linux - darwin -# In addition to a pre-existing windows failure, -# qtlocation/ac899dcc40bb3ad09307583fb7e6befd45a77b33 / QTBUG-66304 introduced -# a failure in 5.14 -[QtPositioning::positioning] - win32 - linux - darwin -# Open GL functions failures on macOS (2/2020) -[QtQml::qqmlnetwork_test] - linux ci # extended, see PyPy section below [QtAsyncio::qasyncio_test_chain] - win32 + py3 [QtQml::bug_825] - py3.9.0 - py3.9.1 - py3.9.2 - py3.9.3 - py3.9.4 - py3.9.5 - py3.9.6 - py3.9.7 - py3.9.8 - py3.9.9 - py3.9.10 - py3.9.11 - # fixed in 3.9.12 py3.10.0 py3.10.1 py3.10.2 py3.10.3 # fixed in 3.10.4 +[QtWebEngineCore::qwebenginecookiestore_test] + darwin ci # PYSIDE-535: These errors are still present. Please try to remove one :) [sample::mixed_mi] pypy @@ -80,4 +42,8 @@ pypy [QtQml::qqmlincubator_incubateWhile] pypy # gives timeout on macOS if I run video converter while testing +[QtRemoteObjects::integration_test] + darwin # PYSIDE-3274, fails for Universal build on macOS 26 +[QtDataVisualization::datavisualization_test] + darwin # PYSIDE-3274, fails for Universal build on macOS 26 # 2022-03-04 PyPy: Currently 28 of 657 tests fail diff --git a/build_scripts/__init__.py b/build_scripts/__init__.py index 17975e34..3df279de 100644 --- a/build_scripts/__init__.py +++ b/build_scripts/__init__.py @@ -6,6 +6,7 @@ from pathlib import Path PYSIDE = 'pyside6' PYSIDE_MODULE = 'PySide6' SHIBOKEN = 'shiboken6' +SHIBOKEN_GENERATOR = 'shiboken6_generator' PYSIDE_PYTHON_TOOLS = ["metaobjectdump", "deploy", diff --git a/build_scripts/config.py b/build_scripts/config.py index efafc768..852c411f 100644 --- a/build_scripts/config.py +++ b/build_scripts/config.py @@ -7,7 +7,7 @@ import sys from pathlib import Path from typing import Any -from . import PYPROJECT_PATH, PYSIDE, PYSIDE_MODULE, SHIBOKEN +from . import PYPROJECT_PATH, PYSIDE, PYSIDE_MODULE, SHIBOKEN, SHIBOKEN_GENERATOR from .log import LogLevel, log from .utils import available_pyside_tools, Singleton @@ -182,7 +182,8 @@ class Config(metaclass=Singleton): "Python bindings for the Qt cross-platform application and UI framework" ) self.setup_kwargs['install_requires'] = [ - f"{self.shiboken_module_st_name}=={package_version}" + f"{self.shiboken_module_st_name}=={package_version}", + 'tomli>=2.0.1; python_version < "3.11"', ] if qt_install_dir: _pyside_tools = available_pyside_tools(qt_tools_path=Path(qt_install_dir)) @@ -240,7 +241,7 @@ class Config(metaclass=Singleton): if self.is_internal_shiboken_module_build(): return SHIBOKEN elif self.is_internal_shiboken_generator_build(): - return f"{SHIBOKEN}_generator" + return SHIBOKEN_GENERATOR elif self.is_internal_pyside_build(): return PYSIDE_MODULE else: @@ -299,7 +300,9 @@ class Config(metaclass=Singleton): Used by PysideBuild.run to build the CMake projects. :return: A list of directory names under the sources directory. """ - if self.is_internal_shiboken_module_build() or self.is_internal_shiboken_generator_build(): + if self.is_internal_shiboken_generator_build(): + return [SHIBOKEN_GENERATOR] + if self.is_internal_shiboken_module_build(): return [SHIBOKEN] elif self.is_internal_pyside_build(): return [PYSIDE, 'pyside-tools'] diff --git a/build_scripts/main.py b/build_scripts/main.py index 02ad4020..2dd6c25e 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -42,7 +42,7 @@ from .utils import (copydir, copyfile, detect_clang, linux_fix_rpaths_for_library, macos_fix_rpaths_for_library, parse_modules, platform_cmake_options, remove_tree, run_process, run_process_output, update_env_path, which) -from . import PYSIDE, PYSIDE_MODULE, SHIBOKEN +from . import PYSIDE, PYSIDE_MODULE, SHIBOKEN, SHIBOKEN_GENERATOR from .wheel_override import get_bdist_wheel_override, wheel_module_exists from .wheel_utils import (get_package_timestamp, get_package_version, macos_plat_name, macos_pyside_min_deployment_target) @@ -380,9 +380,9 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): self.build_tests = OPTION["BUILDTESTS"] - # Save the shiboken build dir path for clang deployment + # Save the shiboken generator build dir path for clang deployment # purposes. - self.shiboken_build_dir = self.build_dir / SHIBOKEN + self.shiboken_generator_build_dir = self.build_dir / SHIBOKEN_GENERATOR self.log_pre_build_info() @@ -404,8 +404,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): with open(self.internal_cmake_install_dir_query_file_path, 'w') as f: f.write(os.fspath(self.install_dir)) - if (not OPTION["ONLYPACKAGE"] - and not config.is_internal_shiboken_generator_build_and_part_of_top_level_all()): + if not OPTION["ONLYPACKAGE"]: # Build extensions for ext in config.get_buildable_extensions(): self.build_extension(ext) @@ -754,7 +753,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): cmake_cmd.append(f"-DPACKAGE_SETUP_PY_PACKAGE_TIMESTAMP={timestamp}") if extension.lower() in [SHIBOKEN]: - cmake_cmd.append("-DUSE_PYTHON_VERSION=3.9") + cmake_cmd.append("-DUSE_PYTHON_VERSION=3.10") cmake_cmd += platform_cmake_options() @@ -993,7 +992,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): "-L", # Lists variables "-N", # Just inspects the cache (faster) "-B", # Specifies the build dir - str(self.shiboken_build_dir) + str(self.shiboken_generator_build_dir) ] out = run_process_output(cmake_cmd) lines = [s.strip() for s in out] diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py index e023ff00..55b92873 100644 --- a/build_scripts/platforms/windows_desktop.py +++ b/build_scripts/platforms/windows_desktop.py @@ -12,9 +12,11 @@ from pathlib import Path from ..log import log from ..config import config from ..options import OPTION -from ..utils import (copydir, copyfile, copy_qt_metatypes, download_and_extract_7z, - filter_match, makefile, in_coin, coin_job_id, copy_cmake_config_dirs) -from .. import PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_MULTIMEDIA_LIBS +from ..utils import (copydir, copyfile, copy_qt_metatypes, + download_and_extract_7z, filter_match, makefile, in_coin, coin_job_id, + copy_cmake_config_dirs) +from .. import (PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_MULTIMEDIA_LIBS, + SHIBOKEN_GENERATOR) def prepare_packages_win32(pyside_build, _vars): @@ -90,7 +92,7 @@ def prepare_packages_win32(pyside_build, _vars): # {shibokenmodule}.pdb file. # Task-number: PYSIDE-615 copydir( - f"{{build_dir}}/{SHIBOKEN}/generator", destination_dir, + f"{{build_dir}}/{SHIBOKEN_GENERATOR}/generator", destination_dir, _filter=pdbs, recursive=False, _vars=_vars) @@ -370,12 +372,12 @@ def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars): file_filter_function=qt_dll_filter, recursive=False, _vars=_vars) + pdb_pattern = "*{}.pdb" if copy_plugins: is_pypy = "pypy" in pyside_build.build_classifiers # /plugins/* -> /{st_package_name}/plugins plugins_target = f"{destination_qt_dir}/plugins" plugin_dll_patterns = ["*{}.dll"] - pdb_pattern = "*{}.pdb" if copy_pdbs: plugin_dll_patterns += [pdb_pattern] plugin_dll_filter = functools.partial(qt_build_config_filter, plugin_dll_patterns) diff --git a/build_scripts/qfp_tool.py b/build_scripts/qfp_tool.py index b1af14a4..983bc185 100644 --- a/build_scripts/qfp_tool.py +++ b/build_scripts/qfp_tool.py @@ -140,11 +140,11 @@ def expand_reference(cache_dict, value): """Expand references to other keys in config files $(name) by value.""" pattern = re.compile(r"\$\([^)]+\)") while True: - match = pattern.match(value) - if not match: + m = pattern.search(value) + if not m: break - key = match.group(0)[2:-1] - value = value[:match.start(0)] + cache_dict[key] + value[match.end(0):] + key = m.group(0)[2:-1] + value = value[:m.start(0)] + cache_dict[key] + value[m.end(0):] return value diff --git a/build_scripts/qtinfo.py b/build_scripts/qtinfo.py index 352fad46..ad80eff8 100644 --- a/build_scripts/qtinfo.py +++ b/build_scripts/qtinfo.py @@ -228,9 +228,7 @@ class QtInfo: self._query_dict[key] = result # Get mkspecs variables and cache them. - # FIXME Python 3.9 self._query_dict |= other_dict - for key, value in self._get_cmake_mkspecs_variables().items(): - self._query_dict[key] = value + self._query_dict |= self._get_cmake_mkspecs_variables().items() def _get_cmake_mkspecs_variables(self): setup_script_dir = Path.cwd() diff --git a/build_scripts/setup_runner.py b/build_scripts/setup_runner.py index ec679801..116ab4a7 100644 --- a/build_scripts/setup_runner.py +++ b/build_scripts/setup_runner.py @@ -126,7 +126,7 @@ class SetupRunner: # instead of shiboken_generator_option_name, but it will # actually build the generator. host_cmd = self.new_setup_internal_invocation( - initialized_config.shiboken_module_option_name, + initialized_config.shiboken_generator_option_name, extra_args=extra_host_args, replace_command_with="build") @@ -228,19 +228,14 @@ class SetupRunner: # target invocations. if config.is_cross_compile(): extra_args = self.add_host_tools_setup_internal_invocation(config) + else: + self.add_setup_internal_invocation( + config.shiboken_generator_option_name) self.add_setup_internal_invocation( config.shiboken_module_option_name, extra_args=extra_args) - # Reuse the shiboken build for the generator package instead - # of rebuilding it again. - # Don't build it in a cross-build though. - if not config.is_cross_compile(): - self.add_setup_internal_invocation( - config.shiboken_generator_option_name, - reuse_build=True) - self.add_setup_internal_invocation(config.pyside_option_name, extra_args=extra_args) diff --git a/build_scripts/utils.py b/build_scripts/utils.py index 3cb7ade0..872130b5 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -7,6 +7,7 @@ import fnmatch import glob import os import re +import shlex import shutil import stat import subprocess @@ -28,7 +29,6 @@ try: except NameError: WindowsError = None - class Singleton(type): _instances = {} @@ -395,9 +395,25 @@ def macos_get_rpaths(libpath): return rpaths +def macos_codesign_adhoc(file_path): + """Signs file_path again with an ad-hoc signature. + + A code signature stores hashes of the file contents inside the file + itself, so editing a Mach-O binary invalidates it. install_name_tool + rebuilds the signature on its own, but only when the existing one is + linker-signed, where there is no signer to preserve. Qt binaries from + the online installer are signed by The Qt Company, so they are left + invalid instead, and macOS refuses to run them on Apple Silicon. An + ad-hoc signature is enough to make them runnable again. + """ + back_tick(f"codesign --force --sign - {shlex.quote(os.fspath(file_path))}") + + def macos_add_rpath(rpath, library_path): try: back_tick(f"install_name_tool -add_rpath {rpath} {library_path}") + # Adding the rpath above invalidated the code signature. + macos_codesign_adhoc(library_path) except RuntimeError as e: print(f"Exception {type(e).__name__}: {e}") @@ -1159,9 +1175,10 @@ def copy_cmake_config_dirs(install_dir, st_build_dir, st_package_name, cmake_pac shutil.rmtree(dst_path) dst_path.mkdir(parents=True) - # check for wheel target files + # check for wheel target and config spec files wheel_path = wheel_cmake_dir / src_path.name wheel_targets_exist = {} + wheel_config_files_exist = set() if wheel_path.exists(): for item in wheel_path.iterdir(): if item.is_file() and re.search(r"Targets(-.*)?\.cmake$", item.name): @@ -1170,6 +1187,10 @@ def copy_cmake_config_dirs(install_dir, st_build_dir, st_package_name, cmake_pac wheel_targets_exist[base_name] = True # Copy wheel target file shutil.copy2(str(item), str(dst_path / item.name)) + elif item.is_file() and re.search(r"Config\..+\.cmake$", item.name): + # Wheel-specific config spec file + wheel_config_files_exist.add(item.name) + shutil.copy2(str(item), str(dst_path / item.name)) # Copy remaining files for item in src_path.iterdir(): @@ -1180,6 +1201,8 @@ def copy_cmake_config_dirs(install_dir, st_build_dir, st_package_name, cmake_pac is_pyside_shiboken = base_name in ("PySide6", "Shiboken6", "Shiboken6Tools") if is_pyside_shiboken and base_name in wheel_targets_exist: skip_file = True + elif item.name in wheel_config_files_exist: + skip_file = True if not skip_file: shutil.copy2(str(item), str(dst_path / item.name)) diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py index 79bd6fa0..5c0d4805 100644 --- a/build_scripts/wheel_files.py +++ b/build_scripts/wheel_files.py @@ -197,6 +197,7 @@ def wheel_files_pyside_addons() -> list[ModuleData]: module_Qt3DRender(), module_QtAxContainer(), module_QtBluetooth(), + module_QtCanvasPainter(), module_QtCharts(), module_QtDataVisualization(), module_QtGraphs(), @@ -464,9 +465,13 @@ def module_QtQml() -> ModuleData: _qtlib = [ "libQt6LabsAnimation", "libQt6LabsFolderListModel", + "libQt6LabsPlatform", "libQt6LabsQmlModels*", "libQt6LabsSettings", "libQt6LabsSharedImage", + "libQt6LabsStyleKit", + "libQt6LabsStyleKitImpl", + "libQt6LabsSynchronizer", "libQt6LabsWavefrontMesh", "libQt6QmlCore", "libQt6QmlLocalStorage", @@ -488,9 +493,13 @@ def module_QtQml() -> ModuleData: _metatypes = [ "qt6labsanimation_metatypes.json", "qt6labsfolderlistmodel_metatypes.json", + "qt6labsplatform_metatypes.json", "qt6labsqmlmodels_metatypes.json", "qt6labssettings_metatypes.json", "qt6labssharedimage_metatypes.json", + "qt6labsstylekit_metatypes.json", + "qt6labsstylekitimpl_metatypes.json", + "qt6labssynchronizer_metatypes.json", "qt6labswavefrontmesh_metatypes.json", "qt6packetprotocolprivate_metatypes.json", "qt6qmlcompilerprivate_metatypes.json", @@ -508,13 +517,16 @@ def module_QtQml() -> ModuleData: ] _qml = [ + "Qt/labs/StyleKit", "Qt/labs/animation", + "Qt/labs/assetdownloader", "Qt/labs/folderlistmodel", - "Qt/labs/sharedimage", - "Qt/labs/wavefrontmesh", - "Qt/labs/qmlmodels", "Qt/labs/platform", + "Qt/labs/qmlmodels", "Qt/labs/settings", + "Qt/labs/sharedimage", + "Qt/labs/synchronizer", + "Qt/labs/wavefrontmesh" ] data.lib.append("libpyside6qml") @@ -572,6 +584,9 @@ def module_QtQuick() -> ModuleData: "qt6quicktimeline_metatypes.json", "qt6quickvectorimage_metatypes.json", "qt6quickvectorimagegeneratorprivate_metatypes.json", + "qt6lottie_metatypes.json", + "qt6lottievectorimagegeneratorprivate_metatypes.json", + "qt6lottievectorimagehelpers_metatypes.json", ] _qtlib = [ "libQt6QuickEffects", @@ -581,13 +596,17 @@ def module_QtQuick() -> ModuleData: "libQt6QuickLayouts", "libQt6QuickParticles", "libQt6QuickShapes", + "libQt6QuickShapesDesignHelpers", "libQt6QuickTemplates2", "libQt6QuickTest", "libQt6QuickTimeline", "libQt6QuickTimelineBlendTrees", "libQt6QuickVectorImage", "libQt6QuickVectorImageGenerator", - "libQt6QuickVectorImageHelpers" + "libQt6QuickVectorImageHelpers", + "libQt6Lottie", + "libQt6LottieVectorImageGenerator", + "libQt6LottieVectorImageHelpers", ] data.qtlib.extend(_qtlib) @@ -645,6 +664,12 @@ def module_QtQuickWidgets() -> ModuleData: return data +def module_QtCanvasPainter() -> ModuleData: + data = ModuleData("CanvasPainter") + + return data + + def module_QtXml() -> ModuleData: data = ModuleData("Xml") return data diff --git a/build_scripts/wheel_override.py b/build_scripts/wheel_override.py index b389bb87..ff52fcba 100644 --- a/build_scripts/wheel_override.py +++ b/build_scripts/wheel_override.py @@ -20,9 +20,9 @@ try: from packaging import tags from wheel import __version__ as wheel_version - from wheel.bdist_wheel import bdist_wheel as _bdist_wheel - from wheel.bdist_wheel import get_abi_tag, get_platform - from wheel.bdist_wheel import safer_name as _safer_name + from setuptools.command.bdist_wheel import bdist_wheel as _bdist_wheel + from setuptools.command.bdist_wheel import get_abi_tag, get_platform + from setuptools.command.bdist_wheel import safer_name as _safer_name wheel_module_exists = True except Exception as e: diff --git a/coin/axivion/ci_config.json b/coin/axivion/ci_config.json new file mode 100644 index 00000000..91c670d6 --- /dev/null +++ b/coin/axivion/ci_config.json @@ -0,0 +1,44 @@ +{ + "Project": { + "BuildSystemIntegration": { + "child_order": [ + "GCCSetup", + "CMake", + "LinkLibraries" + ] + }, + "CMake": { + "_active": true, + "_copy_from": "CMakeIntegration", + "build_environment": {}, + "build_options": "-j4", + "generate_options": "--fresh -DDISABLE_PYI=yes -DPython_EXECUTABLE=$(env:VENV_PYTHON_EXECUTABLE)", + "generator": "Ninja" + }, + "GCCSetup": { + "_active": true, + "_copy_from": "Command", + "build_command": "gccsetup --cc gcc --cxx g++ --config ../../../axivion/" + }, + "LinkLibraries": { + "_active": true, + "_copy_from": "AxivionLinker", + "input_files": [ + "build/sources/*/*/lib*.so*.ir" + ], + "ir": "build/$(env:TESTED_MODULE_COIN).ir", + "plugin_files": [ + "build/sources/*/plugins/*/lib*.so*.ir" + ] + } + }, + "_Format": "1.0", + "_Version": "7.6.2", + "_VersionNum": [ + 7, + 6, + 2, + 12725 + ] +} + diff --git a/coin/axivion/ci_config_common.json b/coin/axivion/ci_config_common.json new file mode 100644 index 00000000..56efe4b4 --- /dev/null +++ b/coin/axivion/ci_config_common.json @@ -0,0 +1,36 @@ +{ + "Project": { + "Git": { + "_active": true, + "author_line": "author-mail", + "author_format": "<(.*)>", + "sourceserver_gitdir": "/data/axivion/databases/pyside-setup.git", + "sourceserver_remote_url": "ssh://codereview.qt-project.org:29418/pyside/pyside-setup" + }, + "Project-GlobalOptions": { + "ci_mode": { + "clean_before": false + }, + "directory": "../work/pyside/$(env:TESTED_MODULE_COIN)", + "ir": "$(env:IRNAME)", + "name": "$(env:PACKAGE)_$(env:MODULE)_$(env:TESTED_MODULE_BRANCH_COIN)_$(env:TARGET_OS_COIN)" + } + }, + "Results": { + "Dashboard": { + "ci_mode": { + "directory": "/data/axivion/databases" + }, + "dashboard_url": "https://axivion-srv.ci.qt.io/axivion/" + } + }, + "_Format": "1.0", + "_Version": "7.6.2", + "_VersionNum": [ + 7, + 6, + 2, + 12725 + ] +} + diff --git a/coin/axivion/start_analysis.sh b/coin/axivion/start_analysis.sh new file mode 100755 index 00000000..2bdbc26b --- /dev/null +++ b/coin/axivion/start_analysis.sh @@ -0,0 +1,15 @@ +#!/bin/bash +#Copyright (C) 2024 The Qt Company Ltd +#SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +$HOME/bauhaus-suite/setup.sh --non-interactive +export PATH=/home/qt/bauhaus-suite/bin:$PATH +export BAUHAUS_CONFIG=$(cd $(dirname $(readlink -f $0)) && pwd) +export AXIVION_VERSION_NAME=$(git rev-parse HEAD) +export EXCLUDE_FILES="build/*:/usr/include/*" +export MODULE=$TESTED_MODULE_COIN +export PACKAGE="Pyside" +export IRNAME=build/$TESTED_MODULE_COIN.ir +export INCLUDE_FILES="" +axivion_ci "$@" + diff --git a/coin/dependencies.yaml b/coin/dependencies.yaml index 3c24055f..4fe6cc2a 100644 --- a/coin/dependencies.yaml +++ b/coin/dependencies.yaml @@ -1,12 +1,13 @@ product_dependency: ../../qt/qt5: - ref: "6417867c2da7aa2b1b94fb9ab084b514fb7009bb" + ref: "713a36536903d172f9e6737584d428753c119496" dependency_source: supermodule dependencies: [ "../../qt/qt3d", "../../qt/qt5compat", "../../qt/qtactiveqt", "../../qt/qtbase", + "../../qt/qtcanvaspainter", "../../qt/qtcharts", "../../qt/qtconnectivity", "../../qt/qtdatavis3d", @@ -15,6 +16,7 @@ dependencies: [ "../../qt/qtgraphs", "../../qt/qthttpserver", "../../qt/qtimageformats", + "../../qt/qtlottie", "../../qt/qtlocation", "../../qt/qtpositioning", "../../qt/qtmultimedia", diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index 478a0765..4e5df4de 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -168,6 +168,13 @@ instructions: condition: property property: host.os equals_value: Windows + - type: PrependToEnvironmentVariable + variableName: PATH + variableValue: "{{ index .Env \"PYTHON3.10.0-64_PATH\"}}\\Scripts;" + enable_if: + condition: property + property: host.os + equals_value: Windows - type: PrependToEnvironmentVariable variableName: PATH variableValue: "/Users/qt/.local/bin/:" @@ -335,6 +342,48 @@ instructions: property: host.osVersion not_in_values: [RHEL_8_6, RHEL_8_8, RHEL_8_10] + + - type: ExecuteCommand + command: "pyenv install 3.11.9" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 1200 + ignoreExitCode: true + enable_if: + condition: and + conditions: + - condition: property + property: host.osVersion + in_values: [MacOS_13, MacOS_14] + - condition: property + property: host.arch + equals_value: ARM64 + userMessageOnFailure: > + Failed to install python 3.11 + - type: PrependToEnvironmentVariable + variableName: PATH + variableValue: "/Users/qt/.pyenv/versions/3.11.9/bin:" + enable_if: + condition: and + conditions: + - condition: property + property: host.osVersion + in_values: [MacOS_13, MacOS_14] + - condition: property + property: host.arch + equals_value: ARM64 + - type: EnvironmentVariable + variableName: interpreter + variableValue: "python3.11" + enable_if: + condition: and + conditions: + - condition: property + property: host.osVersion + in_values: [MacOS_13, MacOS_14] + - condition: property + property: host.arch + equals_value: ARM64 + - type: ExecuteCommand command: "pyenv install 3.13.7" maxTimeInSeconds: 14400 @@ -412,6 +461,7 @@ instructions: command: "pyenv install 3.11.9" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 + ignoreExitCode: true enable_if: condition: and conditions: diff --git a/coin/instructions/execute_axivion_scan.yaml b/coin/instructions/execute_axivion_scan.yaml new file mode 100644 index 00000000..bb1650d7 --- /dev/null +++ b/coin/instructions/execute_axivion_scan.yaml @@ -0,0 +1,100 @@ +analysis_instructions_axivion: &analysis_instructions_axivion + type: Group + instructions: + - type: Group + instructions: + - type: EnvironmentVariable + variableName: AXIVION_CHAINLOAD_TOOLCHAIN_FILE + variableValue: "{{.AgentWorkingDir}}/install/lib/cmake/Qt6/qt.toolchain.cmake" + - type: EnvironmentVariable + variableName: CMAKE_PREFIX_PATH + variableValue: "{{.AgentWorkingDir}}/install/lib/cmake" + - type: Group + instructions: + - type: Rename + sourcePath: "{{.SourceDir}}/coin/axivion/ci_config.json" + targetPath: "{{.Env.HOME}}/axivion/ci_config.json" + userMessageOnFailure: "Moving ci_config.json failed. Make sure you have included the file in coin/axivion/ -folder" + - type: Rename + sourcePath: "{{.SourceDir}}/coin/axivion/start_analysis.sh" + targetPath: "{{.Env.HOME}}/axivion/start_analysis.sh" + userMessageOnFailure: "Moving start_analysis.sh failed. Make sure you have included the file in coin/axivion/ -folder" + - type: Rename + sourcePath: "{{.SourceDir}}/coin/axivion/ci_config_common.json" + targetPath: "{{.Env.HOME}}/axivion/ci_config_common.json" + userMessageOnFailure: "Moving ci_config_common.json failed. Make sure you have included the file in coin/axivion/ -folder" + - type: ExecuteCommand + command: ["../../../axivion/start_analysis.sh"] + maxTimeInSeconds: 28800 + maxTimeBetweenOutput: 28800 + userMessageOnFailure: "Failed to run analysis" + +create_venv: &create_venv + type: Group + instructions: + - type: SetBuildDirectory + directory: "{{.SourceDir}}" + - type: ChangeDirectory + directory: "{{.SourceDir}}" + - type: ExecuteCommand + command: ["python3", "-m", "venv","env"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Failed to create venv" + - type: ExecuteCommand + command: ["{{.SourceDir}}/env/bin/python", "-m", "pip", "install", "-r", "requirements.txt"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Failed to install requirements" + - type: EnvironmentVariable + variableName: VENV_PYTHON_EXECUTABLE + variableValue: "/home/qt/work/pyside/pyside-setup/env/bin/python3" + +build_environment_axivion: &build_environment_axivion + type: Group + instructions: + - type: ExecuteCommand + command: ["sudo", "mkdir", "-p","/data/axivion"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Create mount point for results failed" + - type: ExecuteCommand + command: ["sudo", "mount", "-t", "nfs", "-o", "rw,nfsvers=3", "10.212.0.93:/data/axivion", "/data/axivion"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Mount failed" + - type: ExecuteCommand + command: ["rm","-rf","{{.SourceDir}}"] + maxTimeInSeconds: 100 + maxTimeBetweenOutput: 100 + userMessageOnFailure: "Failed to remove source directory" + - type: MakeDirectory + directory: "{{.SourceDir}}" + - type: ChangeDirectory + directory: "{{.SourceDir}}" + - type: ExecuteCommand + command: ["git", "clone", "--jobs={{.NumCPU}}", "-n","--depth=50", "git://{{.Env.QT_COIN_GIT_DAEMON}}/qt-project/pyside/{{.Env.TESTED_MODULE_COIN}}","."] + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to clone repository" + - type: ExecuteCommand + command: ["git", "fetch", "--recurse-submodules", "origin", "{{.Env.TESTED_MODULE_REVISION_COIN}}"] + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to fetch sources" + - type: ExecuteCommand + command: ["git", "checkout", "--force", "{{.Env.TESTED_MODULE_REVISION_COIN}}"] + maxTimeInSeconds: 900 + maxTimeBetweenOutput: 900 + userMessageOnFailure: "Failed to checkout sources" + +type: Group +instructions: + - *build_environment_axivion + - *create_venv + - *analysis_instructions_axivion +enable_if: + condition: property + property: features + contains_value: PysideAxivion + diff --git a/coin/instructions/execute_build_instructions.yaml b/coin/instructions/execute_build_instructions.yaml index 310f00e9..a9e64a85 100644 --- a/coin/instructions/execute_build_instructions.yaml +++ b/coin/instructions/execute_build_instructions.yaml @@ -19,3 +19,16 @@ instructions: - condition: property property: target.os not_equals_value: Android + - condition: property + property: features + not_contains_value: PysideAxivion + - condition: property + property: features + not_contains_value: DocsGenerationV2 + - type: Group + instructions: + - !include "{{pyside/pyside-setup}}/execute_axivion_scan.yaml" + enable_if: + condition: property + property: features + contains_value: PysideAxivion diff --git a/coin/instructions/execute_test_instructions.yaml b/coin/instructions/execute_test_instructions.yaml index 521503c3..1dfbdb0e 100644 --- a/coin/instructions/execute_test_instructions.yaml +++ b/coin/instructions/execute_test_instructions.yaml @@ -1,8 +1,13 @@ type: Group enable_if: - condition: property - property: features - not_contains_value: LicenseCheck + condition: and + conditions: + - condition: property + property: features + not_contains_value: LicenseCheck + - condition: property + property: features + not_contains_value: PysideAxivion instructions: - type: ExecuteCommand command: "python3 -m pip install -r requirements-coin.txt" @@ -15,7 +20,7 @@ instructions: userMessageOnFailure: > Failed to install requirements-coin.txt on macOS - type: ExecuteCommand - command: "python3 -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=ARM64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}" + command: "python3 -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=ARM64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --osVer={{.Env.HOST_OSVERSION_COIN}}" maxTimeInSeconds: 14400 maxTimeBetweenOutput: 1200 enable_if: diff --git a/coin/instructions_utils.py b/coin/instructions_utils.py index 0337a0a4..dec2d520 100644 --- a/coin/instructions_utils.py +++ b/coin/instructions_utils.py @@ -131,6 +131,8 @@ def get_architecture(ci): def get_python_version(ci): python_ver = "3" + if ci.HOST_OS_VER in ["macos_13", "macos_14"]: + python_ver = "3.11" if ci.TARGET_OS == "Linux" and ci.HOST_ARCH != "aarch64": python_ver = "3.11" elif ci.TARGET_OS == "Windows": diff --git a/coin/module_config.yaml b/coin/module_config.yaml index a9f83666..e663dfcd 100644 --- a/coin/module_config.yaml +++ b/coin/module_config.yaml @@ -5,6 +5,9 @@ accept_configuration: - condition: property property: features contains_value: LicenseCheck + - condition: property + property: features + contains_value: PysideAxivion - condition: and conditions: - condition: property # No cross compilation diff --git a/create_wheels.py b/create_wheels.py index 6f8d8812..c4f8a365 100644 --- a/create_wheels.py +++ b/create_wheels.py @@ -176,7 +176,12 @@ def generate_pyproject_toml(artifacts: Path, setup: SetupData) -> str: # Installing dependencies _dependencies = [] - if _name in ("PySide6", "PySide6_Examples"): + if _name == "PySide6": + _dependencies.append(f"shiboken6=={setup.version[0]}") + _dependencies.append(f"PySide6_Essentials=={setup.version[0]}") + _dependencies.append(f"PySide6_Addons=={setup.version[0]}") + _dependencies.append('tomli>=2.0.1; python_version < "3.11"') + elif _name == "PySide6_Examples": _dependencies.append(f"shiboken6=={setup.version[0]}") _dependencies.append(f"PySide6_Essentials=={setup.version[0]}") _dependencies.append(f"PySide6_Addons=={setup.version[0]}") @@ -185,8 +190,6 @@ def generate_pyproject_toml(artifacts: Path, setup: SetupData) -> str: elif _name == "PySide6_Addons": _dependencies.append(f"shiboken6=={setup.version[0]}") _dependencies.append(f"PySide6_Essentials=={setup.version[0]}") - elif _name == "shiboken6_generator": - _dependencies.append(f"shiboken6=={setup.version[0]}") with open(artifacts / "pyproject.toml.base") as f: content = ( diff --git a/doc/changelogs/changes-6.11.0 b/doc/changelogs/changes-6.11.0 new file mode 100644 index 00000000..b72c5e72 --- /dev/null +++ b/doc/changelogs/changes-6.11.0 @@ -0,0 +1,74 @@ +Qt for Python 6.11.0 is a minor release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - QQmlComponent: Functions create_withownership() and + createWithInitialProperties_withownership() have been + added to return created QML objects with Python ownership. + - [PYSIDE-2716] It is now possible to specify options for pyside6-uic and + pyside6-rcc in a pyproject.toml file. This is useful for + example to disable overly aggressive compression by + pyside6-rcc which may result in files that are not usable + on all platforms. + - [PYSIDE-2786] The minimum supported Python version has been raised to + 3.10. + - [PYSIDE-3168] A number of examples have been updated for Qt 6.11. + - [PYSIDE-3168] The QtCanvasPainter module and an example have been added. + - [PYSIDE-3193] Type hints: The return type of QTranslator.translate() + has been fixed. + - [PYSIDE-3257] pyside6-designer has been fixed to display Python help. + - [PYSIDE-3273] A warning displayed when calling sys.exit() in + a overridden function has been suppressed. + - [PYSIDE-3279] Signal emission has been sped up by code optimizations. + - [QTBUG-131837] The QWebView class of QtWebView and an example + demoing it have been added. + - [QTBUG-143503] It is now possible to populate QML views with JSON data as + shown in the updated colorpaletteclient example. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-1587] The source tree has been restructured; the + shiboken-generator now is in a separate directory + to enable building with scikit-build. + - [PYSIDE-2221] Some basic changes to enable free threaded Python/ + subinterpreter support have been made. + - [PYSIDE-2786] A number of helper functions now superseeded by Python 3.10 + have been removed from libshiboken: _PepUnicode_AsString(), + PepType_GetSlot() and PepErr_GetRaisedException(). + Shiboken::String::compare() has been deprecated. + - [PYSIDE-3245] The support for synthesizing comparison operators in C++ 20 + has been reimplemented and improved. + - [PYSIDE-3245] Non-homogeneous reverse free comparison operators (trailing + class argument) are now ignored by the code generator, + potentially causing warnings about unmatched comparison + operator modifications. The warnings can be ignored, or, + the modification signatures can be rewritten to be + const member operators, which is the recommended way to + ensure compatibility with C++ 20 operator synthesis. + - [PYSIDE-3105] The cross building support has been extended. + - [PYSIDE-3155] libshiboken's warning/error messages are now prefixed. + - [PYSIDE-3168] Only public static fields are generated (even with + the protected hack). + - [PYSIDE-3253] A typesystem attribute to convert null smart pointers + to None has been added. + - [PYSIDE-3282] Keyword arguments passed to base classes in case + of cooperative multiple inheritance are now filtered. + - [PYSIDE-3285] Nested types have been removed from the generated module's + dictionaries. diff --git a/doc/changelogs/changes-6.11.1 b/doc/changelogs/changes-6.11.1 new file mode 100644 index 00000000..63761cc5 --- /dev/null +++ b/doc/changelogs/changes-6.11.1 @@ -0,0 +1,55 @@ +Qt for Python 6.11.1 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-1612] Nuitka dependency of pyside6-deploy is updated to 4.0. + - [PYSIDE-3288] It is now possible to defer deletion of QObjects in case + they get garbage-collected by a thread different from their + owner thread and thus ensure the correct thread affinity. + - [PYSIDE-3031] A memory leak in buffer protocol handling of QByteArray + has been fixed (Non-Limited API only). + - [PYSIDE-3051] The mappings and type hints for the division operator + have been fixed. + - [PYSIDE-3105] Cross compilation support has been improved. + - [PYSIDE-3243] A missing library error occurring in the PySidePlugin + on Windows has been fixed (Non-Limited API only). + - [PYSIDE-3288] libpyside: Warning messages are now prefixed. + - [PYSIDE-3290] Deployment: QML files not being included in deployment + have been fixed. + - [PYSIDE-3294] Qt Widgets Designer plugin: It is now possible to use + inner classes as custom widgets. The interoperability + between Python custom widgets and C++ custom widgets + (with or without namespaces) has been improved. + - [PYSIDE-3295] The CMake build has been fixed. + - [PYSIDE-3297] Qt Labs libraries new in 6.11.have been added. + - [PYSIDE-3299] Deployment: Multi-line arrays in the TOML config file are + now also properly parsed when using Python <3.11. + - [PYSIDE-3304] type hints: The type hints for QMetaObject.invokeMethod() + have been fixed. + - [PYSIDE-3307] A crash occurring when using unittest.mock/patch on + slot receivers has been fixed. + - [QTBUG-71856] The document viewer demo has been updated, now featuring + switching UI language changes at runtime. + - [QTBUG-130067] An example demoing the newly added Qt Quick Controls + StyleKit example has been added. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-3286] Clang version 22 is now supported. diff --git a/doc/changelogs/changes-6.11.2 b/doc/changelogs/changes-6.11.2 new file mode 100644 index 00000000..c1c8dfbc --- /dev/null +++ b/doc/changelogs/changes-6.11.2 @@ -0,0 +1,55 @@ +Qt for Python 6.11.2 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide6 * +**************************************************************************** + + - [PYSIDE-1612] pyside6-project: Options for deployment have been added. + - [PYSIDE-1612] Deployment: Nuitka has been updated to 4.1.1. + - [PYSIDE-2206] The buildings part of the OSM buildings demo has been added. + - [PYSIDE-2541] Type hints: The QML decorators type hint has been fixed. + - [PYSIDE-3168] Class QCanvasGradientStop has been added. + - [PYSIDE-3203] Type hints: QSplitter.replaceWidget() has been fixed + to return optional. + - [PYSIDE-3301] The colorpaletteclient example has been updated + and a small server has been added. + - [PYSIDE-3311] pyside6-project/qmltyperegistrar: Empty .qmltypes files + have been fixed. + - [PYSIDE-3339] Type hints: The return type of QObject.parent() has been + fixed. + - [PYSIDE-3347] Documentation: The module index pages have been updated + and some security-relevant documentation has been added. + - [PYSIDE-3353] A crash calling a class method, such as QObject::tr() + using the snake case feature has been fixed. + - [PYSIDE-3356] Exception handling in QCoreApplication.processEvents() + has been fixed. + - [PYSIDE-3360] metaobjectdump: Decorated Python enum classes are now + recognized. + - [PYSIDE-3380] A crash using QAction.menu() has been fixed. + - [PYSIDE-3406] Support for developer versions of libclang with + non-standard values of LLVM_PACKAGE_VERSION has been + improved. + - [PYSIDE-3417] The Qt6QuickShapesDesignHelpers library missing from + the wheel has been added. + +**************************************************************************** +* Shiboken6 * +**************************************************************************** + + - [PYSIDE-3308] User-added global functions can now also be marked as + deprecated. + - [PYSIDE-3365] The formatting of children/referenced objects in + Shiboken6.dump() has been improved. diff --git a/examples/bluetooth/heartrate_game/HeartRateGame/qmldir b/examples/bluetooth/heartrate_game/HeartRateGame/qmldir index 2baa74a9..11c78c05 100644 --- a/examples/bluetooth/heartrate_game/HeartRateGame/qmldir +++ b/examples/bluetooth/heartrate_game/HeartRateGame/qmldir @@ -12,3 +12,7 @@ Stats 1.0 Stats.qml StatsLabel 1.0 StatsLabel.qml TitleBar 1.0 TitleBar.qml Main 1.0 Main.qml +typeinfo devicehandler.qmltypes +typeinfo connectionhandler.qmltypes +typeinfo bluetoothbaseclass.qmltypes +typeinfo devicefinder.qmltypes diff --git a/examples/bluetooth/lowenergyscanner/Scanner/qmldir b/examples/bluetooth/lowenergyscanner/Scanner/qmldir index 0adf6fb1..d854421c 100644 --- a/examples/bluetooth/lowenergyscanner/Scanner/qmldir +++ b/examples/bluetooth/lowenergyscanner/Scanner/qmldir @@ -1,5 +1,4 @@ module Scanner -typeinfo scanner.qmltypes Characteristics 1.0 Characteristics.qml Devices 1.0 Devices.qml Dialog 1.0 Dialog.qml @@ -8,3 +7,4 @@ Label 1.0 Label.qml Main 1.0 Main.qml Menu 1.0 Menu.qml Services 1.0 Services.qml +typeinfo device.qmltypes diff --git a/examples/canvaspainter/hellowidget/canvaswidget.py b/examples/canvaspainter/hellowidget/canvaswidget.py new file mode 100644 index 00000000..8174b1f4 --- /dev/null +++ b/examples/canvaspainter/hellowidget/canvaswidget.py @@ -0,0 +1,77 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCanvasPainter import (QCanvasPainter, QCanvasPainterWidget, + QCanvasImagePattern, QCanvasRadialGradient) +from PySide6.QtGui import QColor, QFont, QImage +from PySide6.QtCore import QPointF, Qt + +import rc_hellowidget # noqa F401 + + +class CanvasWidget(QCanvasPainterWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self.m_image = None + self.setFillColor(Qt.GlobalColor.white) + + def initializeResources(self, p): + assert (self.m_image is None) + flags = QCanvasPainter.ImageFlag.Repeat | QCanvasPainter.ImageFlag.GenerateMipmaps + image = QImage(":/qt-translucent.png") + assert (not image.size().isEmpty()) + self.m_image = p.addImage(image, flags) + + def graphicsResourcesInvalidated(self): + self.m_image = None + + def paint(self, p): + size = min(self.width(), self.height()) + centerX = self.width() / 2 + centerY = self.height() / 2 + + # Paint the background circle + gradient1 = QCanvasRadialGradient(centerX, centerY - size * 0.1, size * 0.6) + gradient1.setStartColor(QColor(0x909090)) + gradient1.setEndColor(QColor(0x404040)) + p.beginPath() + p.circle(QPointF(centerX, centerY), size * 0.46) + p.setFillStyle(gradient1) + p.fill() + p.setStrokeStyle(QColor(0x202020)) + p.setLineWidth(size * 0.02) + p.stroke() + # Hello text + p.setTextAlign(QCanvasPainter.TextAlign.Center) + p.setTextBaseline(QCanvasPainter.TextBaseline.Middle) + font1 = QFont() + font1.setWeight(QFont.Weight.Bold) + font1.setItalic(True) + font1.setPixelSize(round(size * 0.08)) + p.setFont(font1) + p.setFillStyle(QColor(0xB0D040)) + p.fillText("HELLO", centerX, centerY - size * 0.18) + + # QCanvasPainter text + font2 = QFont() + font2.setWeight(QFont.Weight.Thin) + font2.setPixelSize(round(size * 0.11)) + p.setFont(font2) + p.fillText("Qt Canvas Painter", centerX, centerY - size * 0.08) + + # Paint heart + pattern = QCanvasImagePattern(self.m_image, centerX, centerY, size * 0.08, size * 0.05) + p.setFillStyle(pattern) + p.setLineCap(QCanvasPainter.LineCap.Round) + p.setStrokeStyle(QColor(0xB0D040)) + p.beginPath() + p.moveTo(centerX, centerY + size * 0.3) + p.bezierCurveTo(centerX - size * 0.25, centerY + size * 0.1, + centerX - size * 0.05, centerY + size * 0.05, + centerX, centerY + size * 0.15) + p.bezierCurveTo(centerX + size * 0.05, centerY + size * 0.05, + centerX + size * 0.25, centerY + size * 0.1, + centerX, centerY + size * 0.3) + p.stroke() + p.fill() diff --git a/examples/canvaspainter/hellowidget/doc/hellowidget.rst b/examples/canvaspainter/hellowidget/doc/hellowidget.rst new file mode 100644 index 00000000..732685ff --- /dev/null +++ b/examples/canvaspainter/hellowidget/doc/hellowidget.rst @@ -0,0 +1,61 @@ +Qt Canvas Painter - Hello Widget Example +======================================== + +The example demonstrates the use of +:class:`~PySide6.QtCanvasPainter.QCanvasanvasPainter` +and :class:`~PySide6.QtCanvasPainter.QCanvasPainterWidget` + +The example implements a ``QCanvasPainterWidget`` subclass. One or more instances +of this widget can then be added into a :class:`~PySide6.QtWidgets.QMdiArea` +inside the :class:`~PySide6.QtWidgets.QMainWindow`. +``QCanvasPainterWidget`` itself derives from +:class:`~PySide6.QtWidgets.QRhiWidget`, and is always using accelerated 3D +rendering via :class:`~PySide6.QtGui.QRhi`. + +Subclasses of ``QCanvasPainterWidget`` will at minimum want to implement +:meth:`~PySide6.QtCanvasPainter.QCanvasPainterWidget.paint`. This example +also uses an image, loaded from a ``PNG`` file. + +The ``paint()`` function can start drawing using the provider ``QCanvasPainter`` +right away. + +See :class:`~PySide6.QtCanvasPainter.QCanvasPainter`, +:class:`~PySide6.QtCanvasPainter.QCanvasBrush`, +:class:`~PySide6.QtCanvasPainter.QCanvasRadialGradient`, +:class:`~PySide6.QtCanvasPainter.QCanvasImagePattern`, +:class:`~PySide6.QtCanvasPainter.QCanvasImage` and +:class:`~PySide6.QtGui.QFont` for more information on the features used by +this example. + +The image is used as a pattern, for filling the heart shape. + +When resources like ``QCanvasImage`` and +:class:`~PySide6.QtCanvasPainter.QCanvasOffscreenCanvas` and +are involved, these are +managed in ``QCanvasPainterWidget.initializeResources()`` and +``QCanvasPainterWidget.graphicsResourcesInvalidated()``. + +``initializeResources()`` is merely a convenience. Instead of implementing it, +one could also write the following in paint(): + +.. code-block:: python + + if self.m_image.isNull(): + self.m_image = p.addImage(QImage(":/qt-translucent.png"), + QCanvasPainter.ImageFlag.Repeat) + +This example does not reparent widgets between windows, so graphics resources +are not going to be lost. It is nonetheless a good pattern to assign a default, +empty object to all ``QCanvasImage`` and ``QCanvasOffscreenCanvas`` variables in +``graphicsResourcesInvalidated()``. + +The main() function creates a ``QMainWindow`` and a ``QMdiArea``. Multiple +instances of the ``CanvasWidget`` class can be added as sub-windows. Due to +``QCanvasPainterWidget.hasSharedPainter()`` defaulting to true, and due to being +placed within the same top-level widget, all the painter widgets will share the +same ``QCanvasPainter`` and the associated rendering infrastructure, instead of +creating dedicated ones. + +.. image:: hellowidget.webp + :width: 720 + :alt: Hello Widget Example Screenshot diff --git a/examples/canvaspainter/hellowidget/doc/hellowidget.webp b/examples/canvaspainter/hellowidget/doc/hellowidget.webp new file mode 100644 index 00000000..2365f99d Binary files /dev/null and b/examples/canvaspainter/hellowidget/doc/hellowidget.webp differ diff --git a/examples/canvaspainter/hellowidget/hellowidget.pyproject b/examples/canvaspainter/hellowidget/hellowidget.pyproject new file mode 100644 index 00000000..5d0a072b --- /dev/null +++ b/examples/canvaspainter/hellowidget/hellowidget.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py", "canvaswidget.py", "hellowidget.qrc"] +} diff --git a/examples/canvaspainter/hellowidget/hellowidget.qrc b/examples/canvaspainter/hellowidget/hellowidget.qrc new file mode 100644 index 00000000..cdeaea12 --- /dev/null +++ b/examples/canvaspainter/hellowidget/hellowidget.qrc @@ -0,0 +1,5 @@ + + + qt-translucent.png + + diff --git a/examples/canvaspainter/hellowidget/main.py b/examples/canvaspainter/hellowidget/main.py new file mode 100644 index 00000000..2bf8eeef --- /dev/null +++ b/examples/canvaspainter/hellowidget/main.py @@ -0,0 +1,41 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from PySide6.QtWidgets import (QApplication, QMainWindow, QMdiArea) +from PySide6.QtGui import QKeySequence +from PySide6.QtCore import QCoreApplication, Slot + +from canvaswidget import CanvasWidget + + +class MainWindow(QMainWindow): + def __init__(self, parent=None): + super().__init__(parent) + + self.mdi = QMdiArea() + self.setCentralWidget(self.mdi) + self.createCanvasWidget() + + fileMenu = self.menuBar().addMenu("File") + fileMenu.addAction("New widget", + QKeySequence(QKeySequence.StandardKey.New), + self.createCanvasWidget) + fileMenu.addAction("Exit", + QKeySequence(QKeySequence.StandardKey.Quit), + qApp.quit) # noqa: F821 + + @Slot() + def createCanvasWidget(self): + canvasWidget = CanvasWidget() + subWin = self.mdi.addSubWindow(canvasWidget) + subWin.resize(500, 500) + canvasWidget.show() + + +if __name__ == "__main__": + app = QApplication(sys.argv) + mainWindow = MainWindow() + mainWindow.resize(1280, 720) + mainWindow.show() + sys.exit(QCoreApplication.exec()) diff --git a/examples/canvaspainter/hellowidget/qt-translucent.png b/examples/canvaspainter/hellowidget/qt-translucent.png new file mode 100644 index 00000000..1e0d6928 Binary files /dev/null and b/examples/canvaspainter/hellowidget/qt-translucent.png differ diff --git a/examples/canvaspainter/hellowidget/rc_hellowidget.py b/examples/canvaspainter/hellowidget/rc_hellowidget.py new file mode 100644 index 00000000..502f6850 --- /dev/null +++ b/examples/canvaspainter/hellowidget/rc_hellowidget.py @@ -0,0 +1,984 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.11.0 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00;[\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x02X\x00\x00\x01\xac\x08\x06\x00\x00\x00\x01\x8d\xd9N\ +\x00\x00\x04\xb5iTXtXML:com.\ +adobe.xmp\x00\x00\x00\x00\x00\x0a\x0a \x0a \x0a \x0a \ + \x0a \ + \x0a \x0a \x0a \ +\x0a \x0a\x0a\xde:r\ +\x8c\x00\x00\x01\x80iCCPsRGB IE\ +C61966-2.1\x00\x00(\x91u\x91\ +\xbbK\x03A\x10\x87\xbf$\x8a\x12#\x0aZXX\x04\ +Q\x0b\x89\x12\x15D\x1b\x8b\x04_\xa0\x16\xc9\x09Fm\ +\x92\xcbKH\xe2q\x97 \xc1V\xb0\x0d(\x886\xbe\ +\x0a\xfd\x0b\xb4\x15\xac\x05AQ\x04\xb1\xd6V\xd1F\xc3\ +9\x97\x04\x22bv\xd9\x9do\x7f;3\xcc\xce\x82]\ +I\xa9i\xa3\xce\x0b\xe9LV\x0fL\xf9\xdc\x8b\xa1%\ +w\xc3+.\x9c\xa5\xd9\x17V\x0dm.8\xa9Ps\ +|\xdec\xb3\xecm\xbf\x95\xab\xb6\xdf\xbf\xa3)\x1a3\ +T\xb05\x0a\x8f\xab\x9a\x9e\x15\x9e\x16\x9e]\xcfj\x16\ +\xef\x08\xb7\xab\xc9pT\xf8L\xd8\xa3K\x81\xc2w\x96\ +\x1e)\xf3\x8b\xc5\x892\x7f[\xac+\x01?\xd8[\x85\ +\xdd\x89_\x1c\xf9\xc5jRO\x0b\xcb\xcb\xe9N\xa7r\ +j\xa5\x1e\xeb%\xaeXf!(\xb6KV'\x06\x01\ +\xa6\xf0\xe1f\x86\x09\xfc\x8c0\xc8\x98\xec#\xf43\xc4\ +\x80\x9c\xa8\x11\xef-\xc5\xcf\xb3&\xb1\xaa\xec\x1ayt\ +VI\x90$\x8bG\xd4\x9cd\x8f\x89\x8d\x8b\x1e\x93\x99\ +\x22o\xf5\xffo_\x8d\xf8\xf0P9\xbb\xcb\x07\xf5\xcf\ +\xa6\xf9\xde\x03\x0d\xdbP,\x98\xe6\xd7\x91i\x16\x8f\xc1\ +\xf1\x04\x97\x99j\xfc\xda!\x8c~\x88^\xa8j\xdd\x07\ +\xd0\xb2\x09\xe7WU-\xb2\x0b\x17[\xd0\xf1\xa8\x85\xf5\ +pIr\xc8\xb2\xc7\xe3\xf0v\x0a\xcd!h\xbb\x01\xe7\ +r\xb9g\x95{N\x1e@\xd9\x90\xaf\xba\x86\xbd}\xe8\ +\x15\xff\x96\x95\x1fSxg\xdd\x1f\xb8\xac\xdf\x00\x00\x00\ +\x09pHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\ +\x9c\x18\x00\x00 \x00IDATx\x9c\xed\xddy\xbc\ +]u}\xef\xff\xd79\xc9\xc9@H\x18\xc2\x0cb\x5c\ +2\xb3Dp\xc6:k\xd5j\xa9Z\x8bE\xebP\xb5\ +\xb7\xbd^\xafV\xeaP{\x97\xd6Z]\xdeZ\xa7\xe2\ +\x95Z\x87\xfeZk\x1d\x7f\xb6Tm\xad\xd3\xb5Nu\ +\xc0\x8a\xa0KA\x84%\x022\x1a2\x87$'9\xb9\ +\x7f|W$@\xce\xc9\xd9g\x0f\xdf\xfd]\xeb\xf5|\ +<\xf6\xe3\x04\x0c\xc9'\xe6\xec\xbd\xdf\xfb\xfb\xfd~>\ +\xdf\x09\x04@^\x97\x13\xc0\x19\xc0\x85\xc0\xafD.G\ +\x92$%l2v\x01c\xe4\x0c\xe0\x9d\x18\xae$I\ +R\x9f\x0cX@^\x97\x0f&\xac\x5c=\x22v-\x92\ +$)}\x9d\x0fXy]>\x12x\x07\xae\x5cI\x92\ +\xa4\x01\xe9t\xc0j\xc2\xd5\x9b\x80\x07\xc7\xaeE\x92$\ +\xb5\xc7D\xec\x02bi\xb6\x05\xdfA\x08W\x9d\x0e\x9a\ +\x92$i\xb0:\x17\xb0\xec\x16\x94$I\xc3\xd6\xc5\x95\ +\x1b\xbb\x05%I\xd2Pu*`\xd9-(I\x92F\ +\xa13\x01\xcbnAI\x924*\x9d\x08Xv\x0bJ\ +\x92\xa4Qj\xfd!w\xbb\x05%I\xd2\xa8\xb56`\ +\xd9-(I\x92bi\xf3\x8a\x8e\xdd\x82\x92$)\x8a\ +V\x06,\xbb\x05%IRL\xad\x0bXv\x0bJ\x92\ +\xa4\xd8Z\x15\xb0\xec\x16\x94$I\xe3\xa05\x87\xdc\xed\ +\x16\x94$I\xe3\x22\xf9\x80e\xb7\xa0$I\x1a7m\ +X\xe9\xb1[P\x92$\x8d\x95\xa4\x03\x96\xdd\x82\x92$\ +i\x1c%\x1b\xb0\xec\x16\x94$I\xe3*\xc9\x80e\xb7\ +\xa0$I\x1ag\xc9\x1dr\xb7[P\x92$\x8d\xbbd\ +\x02\x96\xdd\x82\x92$)\x15)\xad\x00\xd9-(I\x92\ +\x92\x90D\xc0\xb2[P\x92$\xa5d\xec\x03\x96\xdd\x82\ +\x92$)5c\x1d\xb0\xec\x16\x94$I)\x1a\xdbC\ +\xeev\x0bJ\x92\xa4T\x8d]\xc0\xb2[P\x92$\xa5\ +n\x1cW\x86\xec\x16\x94$II\x1b\xab\x80e\xb7\xa0\ +$Ij\x83\xb1\x09Xv\x0bJ\x92\xa4\xb6\x18\x8b\x80\ +e\xb7\xa0$Ij\x93\xe8\x87\xdc\xed\x16\x94$Im\ +\x13-`\xd9-(I\x92\xda*\xe6\x8a\x91\xdd\x82\x92\ +$\xa9\x95\xa2\x04,\xbb\x05%IR\x9b\x8d<`\xd9\ +-(I\x92\xdan\xa4\x01\xcbnAI\x92\xd4\x05#\ +;\xe4n\xb7\xa0$I\xea\x8a\xa1\x07,\xbb\x05%I\ +R\xd7\x8cb%\xc9nAI\x92\xd4)C\x0dXv\ +\x0bJ\x92\xa4.\x1aZ\xc0\xb2[P\x92$u\xd5P\ +\x02\x96\xdd\x82\x92$\xa9\xcb\x06~\xc8\xddnAI\x92\ +\xd4u\x03\x0bXv\x0bJ\x92$\x05\x83\x5ca\xb2[\ +P\x92$\x89\x01\x05,\xbb\x05%I\x92\xee\xd0w\xc0\ +\xb2[P\x92$\xe9\xce\xfa\x0aXv\x0bJ\x92$\xdd\ +\xdd\x82\x0f\xb9\xdb-(I\x92\xb4o=\x07,\xbb\x05\ +%I\x92\xe6\xb6\x90\x95'\xbb\x05%I\x92\xe6\xd0S\ +\xc0\xb2[P\x92$i\xff\xe6\x1d\xb0\xec\x16\x94$I\ +\x9a\x9fy\x05,\xbb\x05%I\x92\xe6o\xbf\x87\xdc\xed\ +\x16\x94$I\xea\xcd\xac\x01\xcbnAI\x92\xa4\x85\x99\ +kE\xcanAI\x92\xa4\x05\xd8g\xc0\xb2[P\x92\ +$i\xe1\xee\x16\xb0\xec\x16\x94$I\xea\xcf\x9d\x02\x96\ +\xdd\x82\x92$I\xfd\xfb\xe5!w\xbb\x05\xa5\x91\xb9\x01\ +\x98\xe9\xf1\xbf9\x1cX:\x84Z$IC0a\xb7\ +\xa04/\xbb\x9a\xc7\xce\xbd~\xbc\x0b\xd8\x04\xdcL\x08\ +M\xd7\x00\xb7\x017\x01\xbfh\x1e\x1b\x81u\xc0\xe6*\ ++\xd6\xf5[D^\x97\xcb\x80C\x80\x03\x80#\x80U\ +\xcd\xd7\xd5\xc0Q\xcd\xe3h\xe0\x18\xe0H`1\xb0h\ +\xaf\xc7\x9e\x7f^\xf0E\xef\x92\xa4\xfd\x9b\xc8\xeb\xf2\xbe\ +\x84nA\x0f\xb4K\xc1\xed\x84\xe0\xb4\x11\xd8\xdc\xfc\xf8\ +zB\x80\xfa\x19p]\xf3\xb8\x01XWeE\xaf\xab\ +Q#\x91\xd7\xe5\x12\xe0\xb8\xbd\x1e\xf7l\x1ek\x08!\ +me\xf3X\x05\xac \x04/I\xd2\x00L\xe4u\xf9\ +u\x5c\xb9Rw\xed\x06\xd6\x13\x02\xd4\xcf\x09\xabO?\ +\x03\xae\x05\xea\xe6\xdf\xfd\xbc\xca\x8a\xad\xd1*\x1c\xb0\xbc\ +.\x17\x11V\xbc\x8eo\x1e\x19!\x80\xdd\x83\xb0\xfau\ +,a%lI\xac\x1a%)u\x13y]\xee\x8e]\ +\x844b\x9b\x80+\x81\x1f\x02?\x06~\xca^\x01\xab\ +Maj\xbe\xf2\xba\x9c$l5\xee\x09X\xf7\x00N\ +\x01N\x06No\xfe\x9d\xdb\x8a\x924O\x06,u\xc5\ +&\xe0+\xc0\xd7\x80K\x08\xdb{k\x81\xf5UVl\ +\x8fY\xd88j\x02\xd7A\xc0\xc1\x84\x03\xf6'\x02\x0f\ +\x03\x1e\x05\x9c\x84\x8d0\x924'\x03\x96\xdal#\xf0\ +E\xe0\x13\xc0\x97\x80\xad\xc0v`\xba\xca\x0a\xbf\xef{\ +\xd0l+.m\x1e\x19\xf0t\xe0\xa9\xc0\xa91\xeb\x92\ +\xa4qe\xc0R[\xec\x06v\x00[\x80\xcb\x80\x0f\x03\ +\x9f\xae\xb2\xe2\xe6\xa8U\xb5Xs\x88\xfe\x01\xc0\xb3\x80\ +'\x11V\xba\x0e\xc0\xd5-I2`)y\xd3\xc0-\ +\xc0\xd5\x84\xd5\xaaOUYqY\xdc\x92\xba'\xaf\xcb\ +C\x81_\x05~\x8b;\xcel\xad\xc4s[\x92:\xca\ +\x80\xa5TM\x13\x0e\xa9\x7f\x13\xf8\x8f\xe6\xb1\xd6\xad\xbf\ +\xb8\x9a\xad\xc4\xfb\x01\x8f#\x9c\xd9z p\x18\x06-\ +I\x1dc\xc0Rj\xb6\x03\xdf\x06>E\x08WU\x95\ +\x15\x1b\xe3\x96\xa4\xbb\xca\xebr1a\x04\xc4\x83\x09a\ +\xeb\xc9\x84\xc1\xa7\x92\xd4\x09\x06,\xa5b\x1a\xf8\x16\xf0\ +\xfe\xe6\xeb5UV\xec\x88[\x92\xf6\xa7\xe9F<\x8c\ +\xd0\x85\xf8L\xe0\x19\x84\xb3Z\x92\xd4j\x06,\x8d\xbb\ +\xdd\x84YU\x7f\x01|\x8609}g\xdc\x92\xd4\xab\ +\xe6J\xaee\x84\xae\xc3\xf3\x81\xa7\x11\xa6\xc7KR+\ +\x19\xb04\xae\xa6\x09s\xaa\xfe\x0axW\x95\x15[\x22\ +\xd7\xa3\x01\xca\xeb\xf21\xc0\xeb\x09\xe7\xb5\x96\xe3\x19-\ +I-c\xc0\xd2\xb8\xd9I\x98\xa8\xfeo\xc0;\xaa\xac\ +\xb8*r=\x1a\x92\xbc.\xa7\x80\xe7\x00\xbf\x0f\x9cF\ +\xe8:\x94\xa4Vp^\x8d\xc6\xc9z\xe0\x93\xc0K\x80\ +W\x1a\xae\xda\xad\xca\x8ai\xe0\xef\x08!\xeb\x9d\xc0\x15\ +\xc0\xae\xa8EI\xd2\x80\xb8\x82\xa5qq\x05p!a\ +\xe5\xea\x1a\xc7-tK^\x97\x07\x12.\x9d\x7f\x01\xe1\ +|\xd6T\xdc\x8a$\xa9?\x06,\xc56\x03|\x14x\ ++py\x95\x15\xdb\x22\xd7\xa3H\x9a\x8e\xc3c\x81s\ +\x80?\x07V\xc7\xadH\x92\x16\xce\x80\xa5\x98\xd6\x02\xaf\ +\x03\xfe\xb1\xca\x8a\x0d\xb1\x8b\xd1xh\xcef\x9d\x0a\xfc\ +\x0dpv\xe4r$iA\x0cX\x8aa\x17\xf0}\xe0\ +O\x80/VY\xe1\xb9\x1b\xddM^\x97G\x01\x7f\x06\ +<\x9bp\xc7\xa1\x9d\x86\xfd\x9b!\x8dsn\x8b\xf0\x8c\ +\xb0\x12g\xc0\xd2\xa8\xdd\x0e|\x16xC\x95\x15\xdf\x8b\ +]\x8c\xc6[^\x97\x07\x03\x7f\x00\xbc\x880\x19\xde\x90\ +\xd5\x9f+\x08\xd7J\x8d\xf3\x90\xde\x03\x80\x87\x03\xa7\xc4\ +.D\xea\xc7\xe2\xd8\x05\xa8S6\x10\xba\xc6\xdeVe\ +\xc5\xf5\xb1\x8b\xd1\xf8\xab\xb2b}^\x97\x17\x00\xd7\x03\ +\xaf\x00\xce\x8c\x5cR\xea\xbe\x03\xfcq\x95\x15\x9bb\x17\ +2\x9bf\xe5\xf2/1`)q.\xc1jTn\x00\ +^\x0b\xbc\xcep\xa5^4\x8d\x0f\x1f\x06^\x0a|1\ +r9\x924/\x06,\x8d\xc2U\xc0\x1f\x01\xef\xf3b\ +f-D\x95\x15\xbb\xab\xac\xf8\x1a!d\xfd\x13\xe1,\ +\x91$\x8d-\x03\x96\x86\xed*\xe0\x95\xc0?;\x82A\ +\x03p\x05\xf0r\xe0C\x18\xb2$\x8d1\xcf`iX\ +v\x03?\x01^Qe\xc5\xa7c\x17\xa3vh\x06\xd0\ +\xfe,\xaf\xcb?%\x5c\x1e\xfdT\x1cJ*i\x0c\xb9\ +\x82\xa5a\xf9\x19\xf0j\xc3\x95\x86\xa1\xca\x8ak\x80\x02\ +\xf84\xe1bpI\x1a+\x06,\x0d\xc35\xc0k\xaa\ +\xac\xb8(v!j\xaf*+~\x02\xbc\x1a\xf8B\xec\ +Z$\xe9\xae\x0cX\x1a\xb4\x1b\x08+\x0b\x1f\x89]\x88\ +\xda\xaf\x09Y\xaf\x02\xbe\x14\xbb\x16I\xda\x9b\x01K\x83\ +\xb4\x0ex\x03\xe1@\xbb\x07\x905\x12UV\xfc\x108\ +\x1fpp\xad\xa4\xb1a\xc0\xd2\xa0\xec\x00\xfe\x1a\xf8\x88\ +\xdd\x82\x8a\xa0\x22t\xab^\x17\xbb\x10I\x02\x03\x96\x06\ +c'p\x11\xf07^\xda\xac\x18\x9a\x15\xd3\xaf\x01%\ +\xb0>r9\x92d\xc0R\xdfv\x03\x97\x00owB\ +\xbbb\xaa\xb2b\x07\xf0\x09\xe0\x83\x84;/%)\x1a\ +\x03\x96\xfau+!\x5c]\x1c\xbb\x10\xa9\xca\x8a\xb5\xc0\ +\xbb\x81\xff\xc4A\xa4\x92\x222`\xa9_\x17\x10\xb6\x07\ +\xa5\xb1Pe\xc5\xe5\xc0\x9b\x81[b\xd7\x22\xa9\xbb\x0c\ +X\xea\xc7'\x81\x0b\x9b\xad\x19i\x9c|\x05xS\xec\ +\x22$u\x97\x01K\x0b\xf5s\xc258\x1ej\xd7\xd8\ +\xa9\xb2b\x1ax\x0f\xf0/\xb1k\x91\xd4M\xdeE\xa8\ +\x85\xd8\x06\xbc\x8e0\xb1]\xf3\x90\xd7\xe5\x22\xc2\x07\x9a\ +I`b\xaf\xaf{\x1ew\xb5\xbby@8K\xb4\xbb\ +\xf9:\x03\xecj\xee\xe4\xd3\x1c\xaa\xac\xd8\x91\xd7\xe5\xf9\ +\xc0C\x80\xa3b\xd7#\xa9[\x0cX\xea\xd5.\xc2\xd6\ +\xe0\xe7\xaa\xac\xd8\x19\xbb\x98q\x92\xd7\xe5$\xb0\x028\ +\xb0\xf9z@\xf3X\x01\x1c\x0e\x1c\x02\x1cA\xb8\xa4\xf8\ +\x08`Q\xf3\xe3U\xdc}5y\x1b\xb0\x91\x10\xa8\xd6\ +\x11\xba\xe2\xd66?\xbe9\xaf\xcb\x0d\xcd\xcf\xd9\xd2<\ +6\x03\x9b\xdd\xae\xbd\x9b\x9f\x03o\x04\xfe\x92\xf0w!\ +I#a\xc0R\xaf\xae\x06\xfe\x9ep%N\xa7\xe5u\ +\xb9\x188\x128\x0e8\x1a8\x068\xbe\xf9\xe7c\x08\ +\xab&G\x10\x82\xd5\xa0\xb7\xe3\xb7\x02\xb7\x017\x11\xfe\ +.\xae\x03\xae\xcf\xeb\xf2z\xe0FB\xb0\xb8\xbe\xca\x8a\ +\xcd\x03\xfe}\x93Re\xc5t^\x97\x9f\x06\x1e\x07<\ +\x85}\xaf\x16J\xd2\xc0\x19\xb0\xd4\x8b\xad\x84\xd5\xabo\ +t\xf5*\x9c\xbc.\xa7\x80\xfb\x00\xf7\x05r \xe3\x8e\ +@u805\xa2R\xf6\xac\x8e\x1d\xb7\xd7\xbf\xdbM\ +X\xf5\xba\x09\xb8\x1e\xb86\xaf\xcb+\x81\xef\x03\xdf\x05\ +n\xe9\xe8\xd6\xe2\xcf\x09\xb3\xb1\xce\x04\xd6\xc4-ER\ +W\x18\xb0\xd4\x8b+\x81\x0fTY\xb11v!\xa3\xd4\ +\x9c\x9f:\x1dx\x22\xf0X\xe0X\xee\xd8\xf2\x1bU\xa0\ +\x9a\x8f\x09\xe0\xa0\xe6q2!pm\x01~A\x18Y\ +pI^\x97\x9f\x05\xbeReEg\xa6\x9dWY\xb1\ ++\xaf\xcb/\x01\x9f\x05^\x00,\x89\x5c\x92\xa4\x0e0\ +`i\xben\x07\xfe\x01\xb8\x9a\xd7\xe5y\xcd\x00\xd5\xd6\xa8\xb2\xe26\xc2\xe0Q\ +I\x1a\x9aV\xbdpj\xe0\xd6\x02\x1f \xb4\xfa\xb7B\ +\xb3%\xf8\x14\xe0\x13\x84\xb3GGF.i\x5c-'\ +\xac\xe8\xbd\x1bxG^\x97\x87D\xaeg\xd0\xfe\x15\xf8\ +^\xec\x22$\xb5\x97\x01K\xb3\xd9M8wuE[\ +:\xcb\xf2\xba\x5cE8k\xf5>\xe04\xc2j\x8d\xe6\ +v0a5\xeb\xfdy]\x9e\xd2\xa2\xd5\xac\x8d\xc0\xdf\ +\x12\xae~\x92\xa4\x81k\xcb\x8b\xa5\x06\xef\x17\x84\xc1\x8c\ +\xb7\xc4.d\x10\xf2\xba<\x06x5\xf0\xbf\x09CB\ +5\x7fS\xc0\xd3\x08\x1dx\x0fk\x06\xaf&\xad\xca\x8a\ +i\xe0\xcb\xb4p\xfb[\xd2x0`i_v\x03\x17\ +\x03\xdfjC\xbb~^\x97k\x807\x00\xaf \x5c\xbc\ +\xac\xdeM\x10\xa6\xd8\xbf\x83p\xaf_\x1b\x5cK\xd8*\ +\x9c\x8e]\x88\xa4\xf61`i_6\x00\xffAx\x03\ +JZ^\x97'\x12\x0e4\xff.\xdd\xee\x10\x1c\x94\xfb\ +\x01o\xcf\xeb\xf2\x9c\xd8\x85\xf4\xab\xca\x8aM\xc0W\x81\ +:v-\x92\xda\xc7\x80\xa5}\xb9\x16\xf8L\x95\x15I\ +\x9fOi\xc2\xd5_\x00O\xc5\xef\xf5A:\x0dxk\ +^\x97O\x8b]\xc8\x00|\x97\xb0Z\xdb\x8as\x86\x92\ +\xc6\x87o:\xba\xabi\xe0\xeb\xc0\x8fc\x17\xd2\x8f\xbc\ +.\x8f$\x9c\xb9z2\xae\x5c\x0d\xc3\x89\xc0\x1b\xf2\xba\ +|T\xecB\xfa\xb4\x9ep\x16\xeb\xd6\xc8uHj\x19\ +\x03\x96\xeej3\xf0\xa1\x94\xcf^5S\xc8\xff\x1b\xe1\ +>\xc1\xa5\x91\xcbi\xab\x09\xe0t\xe05y]\x9e\x9a\ +\xd7e*\x17`\xdfI\xd3!\xfby\xe0\xa7\xb1k\x91\ +\xd4.\x06,\xdd\xd5e\xc0\xb7b\x17\xb1Py].\ +&l\x09\xbe\x8c0\xcbI\xc3\xf5H\xc2\xff\xd7\xc9\xce\ +\x13\xab\xb2\xe2z\xc2\xaa\xed\x8e\xd8\xb5Hj\x0f\x03\x96\ +\xee\xea})\xaf^\x01g\x02\x7f\x02\xac\x8e]HG\ +,\x06~\x1b\xf8\xed\xbc.\x97\xc4.\xa6\x0f\x1f'\xac\ +\xdeJ\xd2@\x18\xb0\xb4\xb7\xebH\xf8R\xe7f\xda\xf8\ +\x1b\x09\x87\xb05:\x07\x01\xe7\x03\x0f\x88]H\x1f.\ +\x05.\x89]\x84\xa4\xf60`io\x1f\xab\xb2bC\ +\xec\x22\xfa\xf0J\xc2\xac&\x8d\xde=\x817\xe7u\x99\ +\xe4\x9c\xb1*+v\x00\x1f\x8c]\x87\xa4\xf60`i\ +\x8f]$\xfc\x06\x93\xd7\xe5#\x09\x07\xdb\x17\xc7\xae\xa5\ +\xc3\x1e\x0a\xfca\xec\x22\xfap\x11\xb0.v\x11\x92\xda\ +\xc1\x80\xa5=\xbe\x06\x5c\x13\xbb\x88\x85\xc8\xeb\xf2 \xe0\ +U\xc0\xa1\xb1k\xe9\xb8I\xe0\xfc\xbc.\x93\xdc\xa2m\ +\x06\x8f\xfeK\xec:$\xb5\x83\x01K\x10\x86,~\x06\ +\xd8\x16\xbb\x90^5\xf7\xe2=\x1d\xb8?~?\x8f\x83\ +\x83\x81W\xe4u\x99j\x07\xe7\x87\x81\x9d\xb1\x8b\x90\x94\ +>\xdf\x90\x04a\xc8\xe27I\xf3N\xb65\x84\x80u\ +D\xe4:\x14,\x06\x1eE\x18\xdf\x90\xa2\xef\x01W\xc5\ +.BR\xfa\x0cX\x02\xf8\x0ep}3t1\x19y\ +]N\x01\x8f\x01\xce&\x0c\xbe\xd4x8\x0e87\xaf\ +\xcb\x14Gel\x05\xfe=v\x11\x92\xd2g\xc0\xd2n\ +\xc2`\xd1\x9bb\x17\xb2\x00\xc7\x12\x86\x8a\x1e\x12\xbb\x10\ +\xdd\xc9\x14\xf00\xe0\xac\xd8\x85,\xc0v\xe0+\xa4\xb9\ +\x9a+i\x8c\x18\xb0t#\xf0\xc3*+\x92:\x7f\x95\ +\xd7\xe5$\x90\x03\x8f\x8e]\x8b\xf6\xe9\x04\xe0qy]\ +\xae\x8a]H/\x9a!\xbbW\x03?\x8a]\x8b\xa4\xb4\ +\x19\xb0t%i^\xec\xbc\x02x!^\x873\xae&\ +\x81s\x80,v!\x0bp\x0b\xf0_\xb1\x8b\x90\x946\ +\x03V\xb7\xcd\x10\x02\xd65\x91\xebX\x88\x93\x81'\xc4\ +.Bs:\x05\xf8\x95\xe6\xac\x5cJn#\xdc\xc9\xe9\ +\xdd\x84\x92\x16\xcc\x80\xd5m\xeb\x81\x1fTY\xb15v\ +!\x0b\xf0?p\xf5j\xdcM\x02\xe7\x11V\x1b\x93Q\ +e\xc5N\xc2\x07\x8f\xebb\xd7\x22)]\x06\xacn\xbb\ +\x95\xd0\x96\x9e\x94\xbc.\x8f\x07\x9e\x16\xbb\x0e\xcd\xcb\x03\ +\x083\xcaRsu\xf3\x90\xa4\x051`u\xdb\xcd\xc0\ +\x0fb\x17\xb1\x00\xcf\x22\x0c\xb4\xd4\xf8[\x06\xc0i\xb1\x8b\xe8E\x95\x15\xb7\ +\x02?\xc1\xcb\x9f%-\xc0\xe2\xd8\x05(\x9a/5S\ +\xabS\xf2\x10\xc2\xf58J\xcf2\xe0\xf1\x84\xf9R)\ +\xb9\x188\x97\xd1\xbcV\xde\x0c|\x10\xf8\xe9\x10\x7f\x8f\ ++\x09\xc7\x03\xc6\xd9F\xe0\x1f\x08Wx\xb5\xc5\x22\xe0\ +\x11\xc0o\xc5.D\xa3c\xc0\xea\xae\xaf\xc6.\xa0\x17\ +\xcd\xf6\xe0\x03\x80#b\xd7\xa2\x05Y\x02<\x22\xaf\xcb\ +w6\x07\xc8S\xf1\x1dFw/\xe1:\xe0\x9f\xaa\xac\ +hS\xb0\xe8Y3\x97\xef\x8b\xcd\xa3\x15\x9a\xed\xf1E\ +\x18\xb0:\xc5-\xc2n\xda\x05|=v\x11=:\x86\ +\xb0\xc5\xb44v!Z\x90I`\x0dpj\xe4:z\ +U\x01\x9bc\x17!)=\x06\xacn\xba\xb4\xca\x8a\xd4\ +\xde4Nj\x1eJ\xd7\x11\xc0Y\xb1\x8b\xe8E\x95\x15\ +\xb7\x93\xe00^I\xf1\x19\xb0\xba\xe9+\xb1\x0b\xe8E\ +\xb3=xo\xe0\xf8\xd8\xb5\xa8/\x87\x02g$\xd8M\ +\x98\xdaj\xaf\xa41`\xc0\xea\xa6o\xc7.\xa0G\xab\ +\x80\xd3I\xecN;\xdd\xcdb\xe0\x04\xd2\x9bc\xf6\x8d\ +\xd8\x05HJ\x8f\x01\xab{\xb6\x03\x97\xc4.\xa2G\xab\ +\x09m\xfeJ\xdf\x9a\xe6\x91\x92\xcb\x80\xdbc\x17!)\ +-\x06\xac\xee\xa9Io:\xf5j\xc2\x0a\x96\xd2w\xcf\ +\xe6\x91\x8c\xe6:\xa9\xcbc\xd7!)-\x06\xac\xee\xf9\ +\x11\xa3k;\xef[^\x97\x8b\x80\x13qv\x01=rLC7\xa5\xf6Z\xb4\ +\x19W\xb1$\xcd!\xb5\x175\xf5.\xb53X\x8b\x9b\ +\x87\xbac\xa2y\xa4\xc4\xd5+Is2`I\x8a-\ +\xb5\xcb\x9e\xc1\x80%i?\x0cX\x1a7\xa9\xadd\xa8\ +\x7f)\xce\xc1\xda\x8c!K\xd2\x1cR{QS\xef6\ +\xc4.`\xbe\xf2\xbaL\xf1N:u\x93\x87\xdc%\xcd\ +\xc9\x80\xd5~)\x9d\xc1J\xb1]_\x92\xa4\xbb1`\ +\xb5\x9f\x9f\xb2%I\x1a1\x03\x96\xa4q\xb02\xaf\xcb\ +\xd4\xae\xcb\x91\xa4Y\x19\xb0$I\x92\x06\xcc\x80%I\ +\x924`\x06,I\x92\xa4\x013`I\x92$\x0d\x98\ +\x01K\x92$i\xc0\x0cX\x92$I\x03f\xc0\x92$\ +I\x1a0\x03\x96$I\xd2\x80\x19\xb0$I\x92\x06\xcc\ +\x80%I\x924`\x06,I\x92\xa4\x013`I\x1a\ +\x07\x9b\xaa\xac\xd8\x14\xbb\x08I\x1a\x14\x03V\xfbM\xc5\ +.\xa0\x073\xc0\xce\xd8EH\x92\xd4/\x03V\xfb\x1d\ +\x16\xbb\x80\x1eL\x03\xaeb(\x05Kb\x17 i\xbc\ +\x19\xb0\xdaoq\xec\x02\xe6\xab\xca\x8a\xdd\x84U,i\ +\xdc\xad\xc2\xd7OIs\xf0\x05BRl)\x06\xebd\ +>\xb8H\x8a\xc3\x80\xa5q\x93\xe2\x9b\xad\xfa\xb3\x03\xd8\ +\x10\xbb\x08I\x1a$\x03V\xfb\xdd#v\x01=\xda\x0c\ +l\x89]\x84Fjw\xf3H\xc9\x140\x11\xbb\x08I\ +\xe3\xcb\x80%i\x1c\xa4\xb6j\xb9\x1a_?%\xcd\xc1\ +\x17\x88\xf6[\x16\xbb\x80\x1e\xed\xc4Q\x0d]3\x0dl\ +\x8d]\x84$\x0d\x92\x01\xab\xfd\x0e\x89]@\x8f\xb65\ +\x0fu\xc7.`{\xec\x22zt\x00n\x11J\x9a\x83\ +\x01\xab\xfdR\x1a4\x0ai\x9e\xc7Q\x7fv\x13BV\ +JV\xe2\xeb\xa7\xa49\xf8\x02\xd1~)\x0d\x1a\x85p\ +\xc8}s\xec\x224R;\x80\x8d\xb1\x8b\xe8\x91\xabW\ +\x92\xe6d\xc0j\xbf\x03b\x17\xd0\xa3\x19\xd2;\xf0\xac\ +\xfe\xa4\xb8\x82u\x14\xbe~J\x9a\x83/\x10\xedw`\ +\xec\x02zt{\xf3Pw\xa4\xb8\x82\xb5\x18W\xb1$\ +\xcd\xc1\x80\xd5~\x87\xc6.\xa0G;H\xef\xc0\xb3\xfa\ +\x93\xe2!\xf7U\x18\xb0$\xcd\xc1\x80\xd5~\x07\xc7.\ +\xa0G\xdbH\xef\xcdV\xfd\x99&\xa1\xe1\xb2y].\ +\x06\x96b\xc0\x924\x07\x03V\xfb\xad\xc8\xeb2\xa5\xbf\ +\xe7\xdbq&R\xd7$\x15\xb0\x08\xdb\xee\xa9u\xe7J\ +\x1a\xb1\x94\xdex\xb50\x93\xa4\xd5I\xb8\x9d\xb0M\xa8\ +\xee\xd8IZ\x01k9^\xf6,i?\x0cX\xed7\ +AxCH\xc5f`S\xec\x224R;H\xeb\xef\ +|%\xb0$v\x11\x92\xc6\x9b\x01\xab\xfdR[\xc1\xda\ +\x81+X]\xb3\x93\xb4\xb6\x85W\xe2\x16\xa1\xa4\xfd0\ +`\xb5\xdf$\xa1\xe3)\x09UV\xdcNX\xcdp\x16\ +Vwl%\xad1\x0d\x9e\xc1\x92\xb4_\x06\xac\xf6\x9b\ +\x04\x0e\x8f]D\x8f6\xe1*VW\xcc\x00\x1b\xab\xac\ +H\xe9z$\xb7\x08%\xed\x97\x01\xab\xfd&I\xef\xc2\ +\xe7\xf5\x18\xb0\xbab\x06\xb8%v\x11=Z\x85\x01K\ +\xd2~\x18\xb0\xdao\x92\xf4fa\xddF\x98\x87\xa5\xf6\ +\x9b\x01\xd6\xc6.\xa2G\x07\x13\xe6`I\xd2\xac\x0cX\ +\xed\xb7\x088&v\x11=Z\x87\xc3F\xbbb\x86\x10\ +\xa8S\xe2\x0a\x96\xa4\xfd2`\xb5\xdf$\xb0:v\x11\ +=Z\x8f\x01\xab+v\x017\xc5.\xa2G\x07a\xc0\ +\x92\xb4\x1f\x06\xac\xf6\x9b\x04V\xe7u\x99\xd2\xb5\x1e\xeb\ +p\x8b\xb0+v\x017\xc7.b\xbe\xf2\xba\x5cD8\ +\xd3h\xc0\x924'\x03V\xfbM\x10\xba\x9eV\xc4.\ +\xa4\x07\xb7b\xc0\xea\x8a\x19\x12\x0aX\x84\xe7QJ\xcf\ +%I\x91\x18\xb0\xbaa9im\x13\xdeJZ\x83'\ +\xb5p\xbb\x80\x1bc\x17\xd1\x83\x95\xcdC\x92\xe6d\xc0\ +\xea\x86e$\x14\xb0\xaa\xac\x98\x06~\x81\xc3F\xbb\xe0\ +v\xd2\xea\x22<\xb0yH\xd2\x9c\x0cX\xdd\x90T\xc0\ +j\x5cOX\xddP\xbb\xfd,\xb1!\xa3\x075\x0fI\ +\x9a\x93\x01\xab\x1bR\x0cX\xd7a\xc0\xea\x82\xabb\x17\ +\xd0\xa3U$t\xf5\x94\xa4x\x0cX\xdd\xb0\x9c\xb4.\ +|\x06\xf8\x19\xe1\x12`\xb5\xdbOb\x17\xd0#W\xb0\ +$\xcd\x8b\x01\xab\x1b\x96\x03G\xc4.\xa2G\xd7\xe0\x0a\ +V\x17\xa4\xb6\x82u\x10\xae`I\x9a\x07\x03V7,\ +\x05\x0e\xcf\xebr*v!=\xf8)\xae`u\xc1\x95\ +\xb1\x0b\x98\xaf\xe6\xf9s(a\xcb]\x92\xe6d\xc0\xea\ +\x86\x09\xc2p\xc4\x94>y\xaf%\xbd+T\xd4\xbb\x1f\ +\xc7.\xa0\x07\xcb\x80\xc3\x09\xcf'I\x9a\x93\x01\xab;\ +\x0ei\x1eIh:\xcb~\x10\xbb\x0e\x0d\xd55UV\ +\xa44\xefl\x05pT\xec\x22$\xa5\xc1\x80\xd5\x1d\x87\ +6\x8f\x94\x5c\x16\xbb\x00\x0d\xd5\xf7c\x17\xd0\xa3\x03\x80\ +#c\x17!)\x0d\x06\xac\xeeH1`U\xb1\x0b\xd0\ +P\xa5\xf6\xf7\xbb\x028.v\x11\x92\xd2`\xc0\xea\x8e\ +\xd5\xa47\x0b\xeb{\xb1\x0b\xd0P\xa5\xb6B\xb9\x028\ +:v\x11\x92\xd2`\xc0\xea\x8e\x95\xc0ay].\x8a\ +]H\x0f\xae#\xdcK\xa8vJ\xe6\x8c]^\x97\x13\ +\x84\x15\xe0\x83c\xd7\x22)\x0d\x06\xac\xee\x98\x04\x8e!\ +\x9c#IB\x95\x15;\x81Kc\xd7\xa1\xa1\xb8\x16\xb8\ +%v\x11=X\x0c\xdc\x1b_3%\xcd\x93/\x16\xdd\ +\xb2\x86\xb4F5\x00|;v\x01\x1a\x8a\xab\x80\xe9\xd8\ +E\xf4`1pR\xec\x22$\xa5\xc3\x80\xd5-\xc7\x02\ +\x07\xc6.\xa2G\x17\xc7.@Cq)\xe9\x05\xac\x13\ +b\x17!)\x1d\x06\xacn9\x9e\xf4\x02\xd6\xf7\x80\xcd\ +\xb1\x8b\xd0\xc0\xfd\x80\xb4\x02\xd6\x14\xae`I\xea\x81\x01\ +\xab[\x8e\x22\x1ctOi\x12\xf5&\xe0G\xb1\x8b\xd0\ +@\xad\x07\xaen\xce\xd8\xa5\xe2 \x1c\xd1 \xa9\x07\x06\ +\xacn\x99\x02N$lw\xa4b\x1a\xc75\xb4\xcdU\ +\x84\xab\x90Rr\x06\xb0$v\x11\x92\xd2a\xc0\xea\x9e\ +SH+`\xed\xc0sXms%\xb0.v\x11=\ +:+v\x01\x92\xd2b\xc0\xea\x9e3I\xe8\x93x\xb3\ +\x8dt9\xe9\xbd!k\xdfv\x03?$\xbd\x8b\xbc\x1f\ +\x1c\xbb\x00Ii1`u\xcf\xc9\xc0\xb2\xd8E\xf4h\ +-\xe9]\xab\xa2}[K8\x7f\xb5=v!\xf3\x95\ +\xd7\xe5b\xc2\x07\x13I\x9a7\x03V\xf7\x1c\x06d\xb1\ +\x8b\xe8\xd1\xad\xa4w\xad\x8a\xf6\xedg\xc05\xb1\x8b\xe8\ +QFh\x10\x91\xa4y3`u\xd3Cc\x17\xd0\xa3\ +\x8d\x84N\xc2\xdbc\x17\xa2\xbe\xfd\xb4y\xa4\xe4\xec\xd8\ +\x05HJ\x8f\x01\xab\x9b\x92\x0aXUV\xec\x02\xae \ +\xbd\x95\x0f\xdd\xd9VBPN\xad\x83\xd0\x80%\xa9g\ +\x06\xacnzd\xec\x02\x16\xe0'\x84\xee3\xa5k-\ +pi\x13\x98S\x92\xe2\xf3ERd\x06\xacnZ\x9d\ +\xd7\xe5}b\x17\xd1\xa3\x1b\x08\xdd\x84\xc9\x1c\x8e\xd6\xdd\ +\xdcLb3\xcd\xf2\xba<\x1182v\x1d\x92\xd2c\ +\xc0\xea\xaeG\xc5.\xa0\x17UV\xcc\x00\xdf$\xbcI\ ++=;\x81\x1fTYqM\xecBz\xf4@\x12\x1a\ +k\x22i|\x18\xb0\xba\xebQ\xb1\x0bX\x80\x8b\x81k\ +\x09\xb3\x94\x94\x96\xed\xc0\x17c\x17\xb1\x00\x0f\xc1\x805\ +j\xbb\x81\x99\xd8EH\xfd2`u\xd7\xfd\xf2\xba\x5c\ +\x15\xbb\x88\x1e\xdd\x0c|\x8b\xb4.\x09Vp\x0b\xf0\x85\ +\xd8E\xf4\xa2y~\xe4\xa4u\xf3A\x1bL\x03[b\ +\x17!\xf5\xcb\x80\xd5]\x87\x02\xf7\x8f]D/\xaa\xac\ +\xd8\x0d\xfc\x0b\x8ekH\xd1\xe7\xaa\xac\xb85v\x11=\ +\xdas\xfe*\xa5\xcb\xd1\xdb`\x06?D\xa9\x05\x0cX\ +\xdd\xb5\x844\xdb\xcf\xbf\x0d\xfc8v\x11\xea\xd9\xfbc\ +\x17\xb0\x009\xb0:v\x11\x92\xd2d\xc0\xea\xae%\xc0\ +\xd9y].\x8a]H/\x9a\xbb\x09\xdf\x8b\xe7\xb0R\ +rq\x95\x15\xdf\x8d]D/\xf2\xba\x9c\x04\xeeK\xfb\ +\x02\xd6\x01\xc0\x8a\xd8E\xcc\x83\xcfo%\xcf\x80\xd5]\ +\x93\xc0\xf1\xc0I\xb1\x0bY\x80\xff\x1f\xf8y\xec\x224\ +/\xbb\x80\xbf\x8e]\xc4\x02\xdc\x83\xf0\xdch\xdb\xf9\xab\ +E\xcdc\x9cy\x06K\xad`\xc0\xea\xb6\xc3\x81\xb3b\ +\x17\xd1\xab*+6\x02\x7f\x13\xbb\x0e\xcd\xcb\x8f\x81\xcf\ +\xc6.b\x01N\x00\xee\x1d\xbb\x88\x8e\xda\x85\xf3\xee\xd4\ +\x02\x06\xacn;\x1483\xaf\xcb\xa9\xd8\x85,\xc0\xc7\ +\x08\x17\x07k|\xed\x06>\x02\xac\x8f]H/\xf2\xba\ +\x5c\x0c\x9c\x02\xdc3v-\x1d5\x03\xec\x88]\x84\xd4\ +/\x03V\xb7-\x05N\x05\x8e\x89]\xc8\x02\x5c\x0f|\ +8v\x11\x9a\xd3U\x84\xd1\x0c\xa9\xbdY\x1eB8\x7f\ +\xb5G\x98Y\x96\x9a\xa5\xc0\xaf\ +\xd1\xbe\xee\xc1=R\xe8\x22\x84\xf0=\xe4Aw%\xcd\ +\x80%\x08\xf7\xad\x1d\x1b\xbb\x88\x05\xfa.\xf0I`s\ +\xecB\x04\x84\x83\xedW\x01\x1fk\xba=Ss0\xf0\ +\xab\xb1\x8b\x18\xa2\xe5\xa4q\xb6\xcc\x15,%\xcf\x80%\ +\x08\xddRg6\xddSI\xa9\xb2b\x0b\xf0!\xe02\ +\x1cN8\x0e\xb6\x03\x1f\x07\xbe\x13\xbb\x90\x05:\x1b\xc8\ +b\x171D\x8bIcun\x0b\xce\xc2R\xe2\x0cX\ +\x82\xb0e\xf0\xeb\xc0\xb2\xd8\x85,\xd0\x95\xc0\xbb\xf1\x13\ +\xef8\xb8\x14xO\x95\x15\xa9u\x0e\xee\xf1|\xd2\xd8\ +B[\xa8e\xb8\x82%\x8d\x84\x01K{<\x8e\x04\xcf\ +a\x01TY1CX5\xf9H\xecZ:n;\xf0\ +?\xaa\xac\xb89v!\x0b\x91\xd7\xe5\xf1\xc0\x13b\xd7\ +1dS\xcdc\xdcml\x1eR\xb2\x0cX\xdac\x05\ +\xf0\xdb\xb1\x8bX\xa8*+\xa6\x81?\x04\xbe\x8f[\x85\ +1L\x03\xaf\xa8\xb2\xe2{\xb1\x0b\xe9\xc3\xef\x93F\xf8\ +\xe8\xc7\x0a\xc2}\x84\xe3\xce\x80\xa5\xe4\x19\xb0\xb4\xb7\xdf\ +\xcd\xeb2\x85\xed\x83}\xaa\xb2b+a\x8b\xe7'\xb1\ +k\xe9\x98\xed\xc0?\x02\x7f\x17\xbb\x90\x85\xca\xeb\xf2P\ +\x12\xfe\x80\xd1\x83\xa5\xcdc\xdcm \xb1\x1b\x00\xa4\xbb\ +2`io'\x01\x8f\x89]D\x9f\xbe\x0f\xfc\x19p\ +M\xdc2:c'\xf0E\xe0\xcd\xa4}f\xe6)\xc0\ +\xd1\xb1\x8b\x18\x81\x03Hc\x05k\x0b\xb0\x96\xf0\xfd%\ +%\xc9\x80\xa5\xbbzQ\xa2CG\x01\xa8\xb2b'a\ +6\xd6\xdb\x81\x9fG.\xa7\xedf\x80o\x02%pU\ +\x82\x13\xdb\x01\xc8\xeb\xf2 \xe0i\xa4q\xf8\xbb_\xcb\ +\x80\x15y]\x8e\xf5\xb0\xd1\xe6{\xe9\x06\xec$T\xc2\ +\x0cX\xba\xab\x07\x03\xf7\x8b]D?\xaa\xac\xd8\x04\xfc\ +\x03\xf0\x0e\xe0\xd6\xc8\xe5\xb4\xd5n\xe0G\x84\xd5\xc2\xef\ +TY\x91\xf2\xb5&\x0f\x03\xeeC7^\x0f'\x80\x95\ +\xc0\x92\xd8\x85\xcc\xc3u8\xdfN\x09\xeb\xc2\x0b\x8az\ +s\x10\xf0\xac\xd8E\xf4\xab\x99\xf2e|\xc5\x9c\x00\x00\ +\x14\xb4IDAT\xfe.\xe0\xad\xc0\xb6\xc8\xe5\xb4\xd1\ +\xcf\x80?\x00\xbe\xd2\xac\x1a&\xa99s\xf8$\xe0\xb8\ +\xd8\xb5\x8c\xd0j\xd28\x87U\x13\xcebII2`\ +\xe9\xae\xa6\x80G\xe5uyB\xecB\xfaUe\xc5\xf6\ +*+\xfe\x12x%\xb0)v=-\xb1\x8b\xb0r\xf5\ +\xd8*+\xbe\x91\xf8\xca\x15\xc0\x03\x08+X)\x0c\xdf\ +\x1c\x94\x83I#`]\x8d\x07\xdd\x950\x03\x96\xf6\xe5\ +\x9e\xc09)\x9f\xc5\xda[\x95\x15\xef\x02\x9e\x0b\xfc\x80\ +0N@\x0b\xb3\x11\xf8\x14\xf0\xc4*+\xea\xd8\xc5\xf4\ ++\xaf\xcb\x15\x84\xa6\x8eSc\xd72b\x87\x90\xc0P\ +\xe1*+\xd6\x11\xae]J=\xc4\xab\xa3\x0cX\xda\x97\ +\x83\x81\xc7\x02\xc7\xc7.d\x80>M\xd8\xd2\xfa\x14\xce\ +\xd7\xe9\xd5n\xc2\x96\xe0\xff\x01^Re\xc5u\x91\xeb\ +\xe9[s\xc8\xfb$\xc2\xe1\xf6\xb6\xcf\xbe\xba\xab\xc3H\ + `5\xbe\x86\x97>+Q\x06,\xed\xcb\x04\xf0 \ +\xe0a-Z\xc5\xda\x05|\x1bx\x15\xe1\x5c\xd6\xb58\ +\x90t>v\x01_\xa7\xf9\xff\xad\xca\x8a\xb6tf.\ +!\x9c\xbd:=v!\x11\x1cA\x1a\xa3\x1a\x00\xbe\x84\ +\x07\xdd\x95(\x03\x96fs\x18\xf0D\xe0\xc8\xd8\x85\x0c\ +J\x95\x153\xcd\xd6\xd6\x05\xc0\xf3\x08\xe3\x1c4\xbb\x8d\ +\xc0\xff\x06~\x0f\xb8\xa8\xca\x8a6\x9d\x879\x02x!\ +\xdd:{\xb5\xc7\x11\xa43\x92\xe2\x1a\xe0\x1b\xb1\x8b\x90\ +\x16\xc2\x80\xa5\xd9L\x00O\x06N\x1b\xf7\x999\xbd\xaa\ +\xb2b#\xf0U\xe0w\x80\xf3\x08\x87iu\x87\x19\xe0\ +3\xc0\xaf\x00e\x95\x15W6W\x11\xb5\xc9\x1f\x01k\ +b\x17\x11\xc9!\xc0\xc1)<\xaf\x9b{F\xdf\x15\xbb\ +\x0ei!\xba\xf8\xe9M\xf3w\x10\xe1~\xb6\x8bi\xd9\ +\xb9\xa5\xe6\x85{\x13\xf0\xb1\xbc.\xbf\x00\xfcw\xe0\x05\ +\x84v\xfd\x14:\xac\x06m7a\xa8\xe3\xf7\x81\xb7\x00\ +\x9f\xad\xb2\xa2\x95\xe3-\xf2\xba<\x1dx\x11\xe1CD\ +\x17M\x10\xceW.\x22\x81I\xe9UV\xfc\xdf\xbc.\ +\xff\x9d\xb0\xa2\xde\xd5\xbf3%\xc8\x80\xa5\xfdy\x1a\xf0\ +\xb7\xc0\xe7b\x172,UV\xdc\x06\xbc)\xaf\xcb\x8f\ +\x01\xcf&\x9c\xcdYC\xd8&m\xfb*\xef\x0e\xc2\xc4\ +\xec\x1f\x13\xee\x13\xfc\xd7\x96m\x05\xdeI^\x97+\x09\ +\xdb\x9e]\x0c\xd1{[Cx\xfd\x1f\xfb\x80\xd58\x9f\ +P\xf3)\x18\xb2\x94\x08\x03\x96\xf6g1\xf0\xda\xbc.\ +\xbf\xd9l\xad\xb5V\x95\x15W\x03\xaf\xcf\xeb\xf2\xef\x80\ +\xc7\x01\x8f\x04\xce t\x9b\xa5r(x>f\x80_\ +\x10\xe6Y}\x0f\xf8\xbf\xc0\x97\xab\xach\xf5\xb5$\xcd\ +\x96\xd8\xb9\x84\x0e\xd9\xae\xbb7i\xbd\xfe_\x0d\xfc\x09\ +\xf0\x06BcB\xdb?\xf8\xa8\x05Rz\x82)\x9e\xb3\ +\x09s\xa4:q\x16\xa2\xca\x8ak\x9b\x90\xf5O\x84\x17\ +\xf33\x09\xd7\x07=\x8803)\xd5\xe7\xcdF\xe0\x12\ +\xe0[\x84`\xf5C\xc2\x1d\x82]i\x83?\x95p\xb0\ +=\x95\x03\xde\xc3t\x12\x09}\x1fWY\xb13\xaf\xcb\ +\xcf\x13\xe6\xd8\x9d\x0f<\x1cW!5\xe6&\xf2\xba\xb4\ +U]\xf3q5\xf0\xa4*+\xae\x8c]\xc8\xa8\xe5u\ +\xb9\x98p0\xf8H\xc2\x19\xad\x87\x02\x0f!\x84\xaeC\ +\x19\xdf-\x8b\x9d\x84\xf9U\xffE\x98't\x19p\x13\ +p3\xb09\xd5\xcb\x99\x17\x22\xaf\xcb\x03\x80?\x05^\ +B\xbbV#\x17j\x0bp\xef*+n\x8e]H/\ +\xf2\xba\x9c\x22l\x15\x9eG8Gwt\xd4\x82\xa49\ +\x18\xb04_\xd3\x84\xb3X/n\x0e\x88wR\xb3\xcd\ +\xb4\x84\xf0\xe9y9p2!p\x9d\x09\x9cEx\xf1\ +\x8fu\x91\xeef\xc2\xb4\xfa\x1f\x10Z\xdb\xbfG\xb80\ +w\x1a\xd8\xde\xc2N\xc0y\xcb\xeb\xf2I\xc0\x85t\xb7\ +sp_\x1eYe\xc5Wc\x17\xb1\x10y].%\ +|\xb8\xf9\x1d\xc2\xb9\xc9\xae\x5c\xd6\xad\x84\x18\xb0\xd4\x8b\ +k\x80\xff\x09|\xa6K\xab\x1f\xbdh\xae_9\x81p\ +\x18w\x0d\xa1[\xeb^\x84\xd9C\x87\x13VO&\x9a\ +\xc7|\xde\x10v\xef\xf5\xd8I\xd8\xe6\xbb\x91p0\xfd\ +:\xc2\x0a\xd5O\x80+\x81\x9fw9D\xcd&\xaf\xcb\ +5\xc0_\x01O\x89\x5c\xca\xb8yU\x95\x15o\x89]\ +D\xbf\x9a\x15\xe6{\x13\xae=z\x08\xe1\xc3\xce1\x84\ +\xe7\xd7\x9e\xc7\x9eU\xe6\xdd\x843\x88{~\xbc\xe7\xc1\ +^\xff~\x03\xa1\xc3x7\xe1.\xc4=\xf7\x98\xae\xe3\ +\x8e\xa1\xa7\xbf\x00\xb6\x12\x06\xf1\xae\x03no\xfe\xfd\xcd\ +\x84\xc6\x91\xe9\xe6\xdf\xef\xe9\xc4\xddH\x98\xbb\xf7\xb0>\ +\xff\xb8JH2{\xf0\x1a\x0b\xc7\x03\xcf'\xb4\xf2'\ +\x7f]\xca04\x07\xc5/k\x1ew\x93\xd7\xe5r`\ +\x15\xe1\xaa\x92C\xe6\xf1Kn'\xbc\xc8\xef\xaa\xb2\xe2\ +\x96A\xd5\xd9\x15\xcdJ\xc7\xb9\x84\xceP\xdd\xd9\x03c\ +\x170\x08UV\xec$t\xc1\xfe\x18x7\xfc\xf2\xef\ +\xfdH\xc2*\xd7*\xee\xd8\x16\x9e\x06\xd66?\xdeJ\ +\x08L3\x84\x15\xdeu\xc3\xaa\xb1\xe9^\xf5\xc2\xf9\x8e\ +q\x05K\xbd\xba\x0d\xf8s\xe0}UVl\x8d]\x8c\ +4\x97\xbc.\x1f\x0f|\x008*v-c\xe8:\xe0\ +^\xcd5R\x1a\xa2\xbc.\x0f$\x1c\xb1xF\xecZ\ +4:\xeeY\xabW\x87\x12\xaeN\xb9o\x0a\x93\xa0\xd5\ +]y]\x9e\x00\xbc\x09\xc3\xd5l\x8e\x05\xb2\xd8Et\ +\xc4\x9e\xc1\xc6\xea\x10\x03\x96\x16\xe24\xe0\xe5\xc0\x8a\xd8\ +\x85H\xfb\x92\xd7\xe5*\xc2\xa5\xde\xf7\x8b]\xcb\x18\x9b\ +$\x9c[\xd2\xf0\xcd\x10\xb6\xfa\xd5!\x06,-\xc4$\ +\xe1\xc0\xf0\x1f\xc4.D\xba\xab\xbc.'\x81W\x03O\ +`|Gh\x8c\x8b\xc7\xc5.\xa0CR\x99\x9a\xaf\x01\ +1`i\xa1\x16\x03e^\x97g\xbbU\xa8q\xd1t\ +\x94=\x8f\xd0\x8c\xb1,r9)8\xbb9\x80\xad\xe1\ +\xdaE\x98A\xa7\x0e1`\xa9\x1fK\x09\x077O\x8b\ +]\x88\xd4\x0c\xa1|4\xf0R\ +\x994\x0c\x07\x03\x7f\x0d<#\xaf\xcb\x03\xf6\xf7\x93\xa5\ +\xf9jf\xae\x9d\x0c\xbc\x1e\xf8-B\x17\x9c\xfaw\x0c\ +\xf0p\xc2\xd8\x06\x0d\x87+X\x1dc\xc0\xd2\xb0\x1cI\ +\xe8\xeazN^\x97\x07E\xaeE\xedq\x06\xf0\x16\xe0\ +\x5c\xc0\xf0>8\x93\x84&\x01\xef&\x1c\x9ei\xec$\ +\xec\x14\x03\x96\x86e\x82\xd0:\xff\x1a\xe0\xf7\x9a\xbb\xe1\ +\xa4\x05\xcb\xeb\xf21\xc0\x07\x80_\x03\x96D.\xa7\x8d\ +N\x04\xce\xc9\xeb\xd2U\xc1\xe1\xd8E8\xe8\xae\x8e0\ +`i\x98&\x80\xe3\x08\xdb9\xff+\xaf\xcb\xe5\x91\xeb\ +Q\x82\xf2\xba\x9c\xcc\xeb\xf2\xf9\xc0\x87\x09CD}\xdd\ +\x1a\x8eE\x84\xfbE\x0f\x8d]HKm\x07n\x8b]\ +\x84F\xc7\x17*\x8d\xc2\x0a\xe0\x8f\x81\x0f\xe6u\xb9\xda\ +\x83\xb4\x9a\x8f\xa6S\xf0P\xe0O\x81w\x10\xb6\x9d5\ +\x5cG\x03/\xf39:\x14\xbb\x09\x13\xdd\xd5\x11>\x89\ +4JO\x07>\x0f<\xc2\x81\xa4\x9aK\xf3\x06\x7f&\ +\xf0v\xe0U\x80\xe7\xf8F\xe7\xc5\x84\xff\xef5X;\ +\x80\x8d\xb1\x8b\xd0\xe8\x18\xb04jg\x01\xef'\x9c\xcb\ +:\xb6\xe9\x0a\x93~\xa9\x09\xdf\xcf\x00\xde\x09<\x17p\ +ky\xb4V\x00\xa5\xd7\xe7\x0c\x9cc\x1a:\xc6\x80\xa5\ +Q\x9b\x00\xeeM\x98\xfa\xfe&\xc2\x80C\xbf\x0f\x05@\ +^\x97\x87\x03\x7fN\xd8\x12|\x18\xde+\x18\xcb\xa3\x80\ +\x17\xe6u\xe9t\xfc\xc1\xb1\x8b\xb0c|cS,\xab\ +\x81\xf3\x08\x93\xb8_\x94\xd7\xa5]a\x820'\xe8\xe1\ +\xc0Q\xb1\x0b\xe9\xb8\xa5\xc0\x0b\x81'\xc4.\xa4E\x5c\ +\xc1\xea\x18\x03\x96bZ\x02\xe4\x84\xb9F\xff\x96\xd7\xe5\ +}#\xd7\xa3\xc8\xaa\xac\xd8\x02\xbc2v\x1d\xfa\xe5J\ +\xf3\x1f\xe5u\x99\xc7.\xa6%\x5c\xc1\xea\x18\x03\x96\xc6\ +\xc1r\xc2}r_\xcc\xeb\xf2O\xf2\xba<\xda\xbb\x0c\ +\xbb\xab\xca\x8a\xaf\x10\xee\xb4\xdc\x1d\xbb\x96\x8e\x9b$l\ +\x15\xbe2\xaf\xcb\xe3\x22\xd7\xd2\x063\x84Q\x0d\xea\x08\ +\x03\x96\xc6\xc9a\xc0\x1b\x81O\x00\xe7\xe6uy\xbc\x87\ +\xe0;\xab\x04~\x18\xbb\x081\x09<\x138?\xafK\ +\xc7d\xf4\xa1\xca\x8a\x1d\xc0\xa6\xd8uht\x0cX\x1a\ +7\x93\xc0C\x09\x9d\x86\x17\x00\xe7\x19\xb4:\xe9f\xc2\ +\x88\x86\xf5\xb1\x0b\x11S\xc0K\x80\xd7\xe4u\xb9&r\ +-\xa9\xdb\x81\xd3\xdc;\xc3\x80\xa5q\xb5\x02x*\xf0\ +\x7f\x80\xbf\x02~\xdf\xa0\xd5\x1dUV\xec\x04>\x0b|\ +\x1a\x873\x8e\x83)\xe0E\xc0[\xf3\xba|P\xecb\ +\x12\xb6\x1d\xb7\x09;\xc3\x80\xa5q\xb7\x1ax\x0aa\xeb\ +\xf0C\xc0\xab\xf2\xba<:nI\x1a\x91\x9b\x81\x7f\x04\ +\xae\x88]\x88\x80p\x95\xceS\x80w\xe6u\xf9\x9by\ +]N\xc5.(A\xdb\xb0\x93\xb03\x0cXJ\xc1$\ +\xe1|\xd6\xaf\x00\x7f\x06\x5c\x9a\xd7\xe5\x05y]\x9e\x14\ +\xb5*\x0dU\x95\x153\xc0\x97\x81O\xe2\x9b\xd2\xb8X\ +\x0c<\x10\xb8\x10xy^\x97N\xd8\xef\xcd4a\x5c\ +\x83:`\x22\xafK;u\x94\xaa\xad\xc0\x97\x80\xf7\x00\ +\xdf&\x1c \xdd^e\x85\xdf\xd3-\xd2\x9c\xfb\xf9\x08\ +\xf0`\x1c<:n\xbe\x08\xfc\x11\xf0\x93*+\xb6\xc5\ +.f\xdc\xe5u\xf9x\xe0]\xc0\x89\xb1k\xd1\xf0\x19\ +\xb0\xd4\x063\xc0\xe5\xc0g\x80/\x00\xd7\x027\x01\x1b\ +\x0d[\xed\x90\xd7\xe5s\x09W\xe7\xb8b2~\xb6\x00\ +\x7f\x0d|\x14\xb8\xaa\xca\x0a\xef\xdb\x9bE^\x97\x8f&\ +\x04\xac\xd3b\xd7\xa2\xe13`\xa9m\xb6\x01\x97\x00\xdf\ +\x00.\x05\xae$\xbc\xe8\xaf\x8bZ\x95\xfa\x92\xd7\xe5r\ +\xe0\xbd\xc0\xb3c\xd7\xa2}\x9a\x01\xae\x02\xfe\x99\xb0\xaa\ +u\x09\xb0\xde\x0f8w\x96\xd7\xe5C\x08\xdb\xab\xf7\x8b\ +]\x8b\x86\xcf\x80\xa5\xb6\xda\x0d\xac\x03\xae&\xbc\xf0_\ +\x0e|\x1f\xb8\x0c\xb8\xbe\xe9RSB\xf2\xba<\x91\xd0\ +Y\x98\xc5\xaeE\xb3\x9a&<\xe7\xbeM\xd8\xbe\xffr\ +\x95\x15\xd7\xc6-)\x9ef`\xf2\x91\x84\xef\xd95\xc0\ +C\x80\xdf\x04l\xd4\xe9\x00\x03\x96\xbab+\xf0\x8b\xe6\ +\xf1SB\xd0\xfa/\xe0\xd2*+n\x8cY\x98\xe6/\ +\xaf\xcbg\x03\x7fG8l\xad\xf15\x03\xdc\x02\x5c\x07\ +|\x07\xf8W\xe0\x1bUVl\x88Z\xd5\x905\x97\x95\ +\xe7\x843V\xa74\x8f\xc3\x08[\xdb\xab\x9a\xc7r<\ +K\xd8\x09\x06,u\xd1\x0c\xe1\x93\xf6\x0e\xc2L\x9ak\ +\x80o\x01\x17\x13\xde\x0c\xael:\xd84f\x9a\xae\xb5\ +\xf7\x01\xe7\xc6\xaeE\xf36M\xd8\xba\xbf\x19\xf8\x0aa\ +\xb6\xd9\x17\xaa\xac\xd8\x1a\xb5\xaa>\xe4u9\x09\xdc\x13\ +\xb8/!D\xdd\x1f\xb8\x0f!LM\x11>\x00\xec\xf9\ +j\x98\xea(\x03\x96tw\x9b\x08[\x8a\x974_\xaf\ +\x04~\x0cl$\xb4X\xef\x22\x84\xb4]{~<\x0e\ +\x81\xac\x19\xc2\xba\xa8y\xecyq?\x88p\xde\xe3I\ +\xc0\x0f\x80w\x8dC\xad\x0b\xd5\xfc\x19\x1f\x02|\x1c\xf0\ +~\xbctm\x07\xbeK\x18\xc3\xf1M\xc2\x8a\xf2fB\ +\x18\xdbE\xf3<\x1b\xd5\xf7\xea]\x9e;{?\x16\x13\ +B\xd3\xc9\xc0I\x84\xd5\xa9=+T+GQ\x9b\xd2\ +e\xc0\x92\xe6\xef&\xe0z\xe0\xe7\xc0\x0d\xcd\xe3F`\ +-\xe1\xbc\xd76\xeeX\x19\xdbIx\x13\x99i\xfey\ +\x86;\xc2\x19\xcd\xd7\xe9y\xfc\x9e\x93\xc0\x92\xbd~<\ +\xc5\x1d\x01jI\xf3u\x19\xb0\x94\xb0\xfdp\x0cp<\ +p*\xe1\x0d\xe1D\xe0\xc0\xe6\xbf\xdf\x02<\xa2\xca\x8a\ +Kz\xfd\x83\x8f\x93\xbc.W\x12F\x03\xfc1a\xbb\ +E\xe9\xdb\xb3\x92|9P\x03?#t\x03\xaf%<\ +\xaf\xb67_wp\xc7,\xa9\x1d\x84\xb3\x96s=\x97\ +\xf6\x00\xdc\ +\x1a\xb9\x1cI2`Ib\x0ax[^\x97\x87\xc4.\ +\xa4\x1f\xcdl\xac\x7fk\x1e\xce\xc6\x92\x14\x95\x01K\x12\ +\xc0\x09\xc0\xcb\xf2\xba\x5c\xb2\xdf\x9f9\xc6\xaa\xac\xb8\x0d\ +\xf8{\xc2\xe5\xdc)O\xaa\x97\x948\x03\x96$\x08\xf7\ +\x1b\xfe6pv\xecB\x06\xe0?\x81\x8f\x11\xceeI\ +R\x14\x06,I{\xdc\x8b0\xe1\xfd\xc8\xd8\x85\xf4\xa3\ +\xca\x8a\x9d\xc0\xfb\x80\x8bc\xd7\x22\xa9\xbb\x0cX\x92\xf6\ +X\x02<\x09xl^\x97\x8bb\x17\xd3\x8f*+n\ +$\xcc\xc6\xda\x16\xbb\x16I\xddd\xc0\x92\xb4\xb7#\x80\ +\x17\x00k\x22\xd71\x08\xdf\x00\xde\x11\xbb\x08I\xddd\ +\xc0\x92\xb4\xb7\x09\xe0\xb1\xc0S[p\xe0}\x06x\x1b\ +pi\xecZ$u\x8f\x01K\xd2\xbe\xfc!pJ\xec\ +\x22\x06`=\xf0f`]\xecB$u\x8b\x01K\xd2\ +\xbe\xdc\x03xM^\x97\xcbc\x17\xd2\x8ff6\xd6\x7f\ +\x00\xff\x8c\xd7\xe8H\x1a!\x03\x96\xa4\xd9<\x058/\ +v\x11\x03p+\xf0\x8f\xc0\xe5\xb1\x0b\x91\xd4\x1d\x06,\ +I\xb3Y\x02\xbc:\xaf\xcb\x13b\x17\xd2\x8f\xe6,\xd6\ +\xb7\x81O\x00\x1b#\x97#\xa9#\x0cX\x92\xe6\x92\x01\ +\xaf\xcc\xeb2\xe9\xd7\x8a*+n\x07\xfe\x16\xa8p\xc2\ +\xbb\xa4\x11H\xfaES\xd2\xd0-\x06\x9e\x00<9\xaf\ +\xcb\x89\xd8\xc5\xf4\xa3\xca\x8a\x1b\x80\xb7\xe3*\x96\xa4\xe1\ +\xda\x0e\xbc\xdd\x80%i\x7f\x8e\x07\x9e\x0f\x1c\x1b\xbb\x90\ +\x01\xb8\xa8yH\xd20\xac\x07\xde\x09\xbc\xc6\x80%i\ +\x7f&\x80G\x00\xbf\x91\xd7\xe5\xb2\xd8\xc5\xf4\xa39\x8f\ +\xf5:\xc2e\xd0\x924Hk\x81\xf7\x00o\xa9\xb2\xe2\ +v\x03\x96\xa4\xf9X\x0d<\x0fH\xfa\xc0;@\x95\x15\ +\xd7\x12B\xd6\xd6\xd8\xb5Hj\x8d\xf5\xc0\xfb\x81\x0b\xaa\ +\xac\xb8\x15<\x83%i\xfe\xce\x02\x9e\x9b\xfa*V\xe3\ +\xdf\x81\x8f\xc7.BR+l'\x5c0\xff\xb6\xe6\x1e\ +T\xc0\x80%i\xfe\xa6\x80\x17\x03g\xc4.d\x006\ +\x01\xef\x06\xae\x8a]\x88\xa4\xe4]\x08\xbcn\xcf\xca\xd5\ +\x1e\x06,I\xbd8\x00\xb8 \xaf\xcb\xa5\xb1\x0b\xe9G\ +\x95\x15\xbb\x81\xef\x13>un\x8b\x5c\x8e\xa44m\x07\ +\xde^e\xc5\xcb\x9bQ0wb\xc0\x92\xd4\xab\x87\x00\ +/\xce\xebrQ\xecB\xfaQe\xc56\xe0\x93\x84\xab\ +tf\x22\x97#)-\xbf\xec\x16\x9c\xed'\x18\xb0$\ +-\xc4\xf9\x843Y\xa9\xbb\x12\xf8 pS\xecB$\ +%\xe3N\xdd\x82\xb3\xfd$\x03\x96\xa4\x858\x0axi\ +^\x97\x87\xc6.\xa4\x1f\xcdV\xe1g\x08\x87\xde\xbd\x0c\ +Z\xd2\xfe\xdc\xad[p6\x06,I\x0b\xb1\x18\xf8U\ +\xe0\x9c\xd8\x85\xf4\xab\xca\x8a\x0d\xc0\xdb\x80\x1b\xf7\xf7s\ +%u\xda>\xbb\x05gc\xc0\x92\xb4PG\x02\xcf\xcc\ +\xeb\xf2\xa4\xd8\x85\x0c\xc0\x15\xc0\x9f\xc7.B\xd2X\xdb\ +g\xb7\xe0l&\xf2\xba\xf4\xe2SI\x0b\xb5\x03x-\ +\xf0\xce\xe6\xd0x\xd2\xf2\xba\xfc\xf0\x12\xc2,\ +\xafe@\x1b\xce\x8eI\x1a\x9c=\xdd\x82=o\x0b\xee\ +\xcd\x15,I\x83\xb4\x018\xa7\xca\x8a\xaf\xc5.d\x8f\ +\xe6\xde\xc4\xa3\x08\xabU/\x05\x8e\xc3P%\xe9\xee\x16\ +\xd4-8\x1bW\xb0$\x0d\xd2J\xe0\xd5y]\xfe\xb8\ +\xca\x8a[b\x16\x92\xd7\xe5A\xc0\x09\x843V\xcf\x05\ +N\xc4`%i\xdf\xd6\x13\x86\x88\xbenP\xbf\xa0\x01\ +K\xd2 M\x02\x0f\x02\x9e\x91\xd7\xe5{\xab\xac\xd81\ +\xca\xdf\xbc9\xb8\xbe\x1ax \xf0\x18\xe0\xa9\x84\x90%\ +I\xb3\xe9\xab[p6\x06,I\x83\xb6\x1ax\x16\xf0\ +M\xe0\xbb\xa3\xfaM\x9b\x83\xeb\x8f'\xacX=\x02\xb8\ +'\x1e\x5c\x974\xb7\xbe\xbb\x05gc\xc0\x924h\x13\ +\xc0\xfd\x80\xa7\xe7uyUs\xd7\xdf\xd0\xe4u\xb9\x02\ +x:\xf0l\xc2L\xae#\xf0\xb5M\xd2\xfe\xed}\xb7\ +\xe0@\xc3\x15x\xc8]\xd2\xf0\xdc\x0c<\x03\xf8Z\x95\ +\x15\x03\x7f\x9d\xc9\xebr%a\xd4\xc2\x1f\x12V\xab\x96\ +\xe3\x8a\x95\xa4\xf9{;\xf0\x9aAn\x0b\xee\xcdOy\ +\x92\x86\xe5HB\xf8\xb9\x8c\xd0]\xd8\xb7\xbc.\xa7\x08\ +[\x90\xbfN\x18\x07q\x0a\x86*I\xbd\x19h\xb7\xe0\ +l\x0cX\x92\x86\xe9\xa9\xc0\xa7\x81\xbf\xef\xe7\x17iV\ +\xab\xd6\x00\x8f\x04~\x9f\xb0\x15h\xb0\x92\xd4\xab\x81w\ +\x0b\xce\xc6-BI\xc3v\x0d\xf0\xb8*+\xae\xee\xf5\ +?l\x06\x83\x9eE\xe8\x08|\x1a!XI\xd2B\xec\ +\xdd-8\xf03Wwe\xc0\x924\x0a\x1f\x04\xfe[\ +\x95\x15\xdb\xe7\xf3\x93\xf3\xba\x5c\x0e\xfc*\xf0$BG\ +\xe0\x09\xc0\xd4\xf0\xca\x93\xd4r\xeb\x81\xf7\x10\xba\x05o\ +\x1c\xc5o\xe8\x16\xa1\xa4Q8\x87\xb0\x02\xf5\xd1\xb9~\ +R\x13\xac\xce\x01\x9e\x03\xdc\x87p\xf9\xb2\xc1JR?\ +\x86\xda-8\x1bW\xb0$\x8d\xc2n\xe0\xeb\xc0\xb3\xaa\ +\xac\xb8\xfe\xae\xff\xe3^\xc1\xeae@\x0e\xac\xc03V\ +\x92\x06c\xa8\xdd\x82\xb31`I\x1a\x95\xcd\xc0[\x81\ +7UY1\x9d\xd7\xe5b`\x15\xf0h\xe0U\xc0\xfd\ +\x81E\x11\xeb\x93\xd4.#\xe9\x16\x9c\x8d[\x84\x92F\ +\xe5@\xe0)\xc07\xf3\xba\xbc\x0e8\x1b\xf8\x03\xc2\xd5\ +:\x924H#\xeb\x16\x9c\x8d\x01K\xd2(\xdd\x07x\ +\x0da(\xe8Y\xb8b%i\xf0\x86r\xb7`\xaf\x0c\ +X\x92Fi1\xf0\xf0\xd8EHj\xad\xa1\xdd-\xd8\ ++\x03\x96$Ij\x83(\xdd\x82\xb31`I\x92\xa4\ +6\xb8\x10x]\xccm\xc1\xbd\x19\xb0$IR\xca\xa2\ +v\x0b\xce\xc6\x80%I\x92R\x15\xbd[p6\x06,\ +I\x92\x94\xa2\xb1\xe8\x16\x9c\x8d\x93\x92%IRj\xc6\ +\xa6[p6\xae`I\x92\xa4\x94\x8cU\xb7\xe0l\x0c\ +X\x92$)%c\xd5-8\x1b\x03\x96$IJ\xc1\ +Xv\x0b\xce\xc6\x80%I\x92\xc6\xdd\xd8v\x0b\xce\xc6\ +\x80%I\x92\xc6\xd9Xw\x0b\xce\xc6.BI\x924\ +\xae\xc6\xbe[p6\xae`I\x92\xa4q\x94D\xb7\xe0\ +l\x0cX\x92$i\x1c%\xd1-8\x1b\x03\x96$I\ +\x1a'Iu\x0b\xce\xc6\x80%I\x92\xc6Er\xdd\x82\ +\xb31`I\x92\xa4q\x90d\xb7\xe0l\xec\x22\x94$\ +I\xb1%\xdb-8\x1bW\xb0$IRLIw\x0b\ +\xce\xc6\x80%I\x92bJ\xba[p6\x06,I\x92\ +\x14C+\xba\x05gc\xc0\x92$I\xa3\xd6\x9an\xc1\ +\xd9\x18\xb0$I\xd2(\xb5\xaa[p6v\x11J\x92\ +\xa4Qi]\xb7\xe0l\x5c\xc1\x92$I\xa3\xd0\xcan\ +\xc1\xd9\x18\xb0$I\xd2(\xb4\xb2[p6\x06,I\ +\x924L\xad\xee\x16\x9c\x8d\x01K\x92$\x0dK\xeb\xbb\ +\x05gc\xc0\x92$I\xc3\xd0\x89n\xc1\xd9\xd8E(\ +I\x92\x06\xad3\xdd\x82\xb3q\x05K\x92$\x0dR\xa7\ +\xba\x05gc\xc0\x92$I\x83\xd4\xa9n\xc1\xd9\x18\xb0\ +$I\xd2 t\xb2[p6\x06,I\x92\xd4\xaf\xce\ +v\x0b\xce\xe6\xff\x01\x0b\xfbZ/N\xb7\xcd\xe8\x00\x00\ +\x00\x00IEND\xaeB`\x82\ +" + +qt_resource_name = b"\ +\x00\x12\ +\x0e\xe6\x0eG\ +\x00q\ +\x00t\x00-\x00t\x00r\x00a\x00n\x00s\x00l\x00u\x00c\x00e\x00n\x00t\x00.\x00p\x00n\ +\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9b!\x83\xa2\x9d\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/charts/donutbreakdown/donutbreakdown.py b/examples/charts/donutbreakdown/donutbreakdown.py index db239918..9e74a717 100644 --- a/examples/charts/donutbreakdown/donutbreakdown.py +++ b/examples/charts/donutbreakdown/donutbreakdown.py @@ -109,7 +109,7 @@ if __name__ == "__main__": # Graph is based on data of: # 'Total consumption of energy increased by 10 per cent in 2010' # Statistics Finland, 13 December 2011 - # http://www.stat.fi/til/ekul/2010/ekul_2010_2011-12-13_tie_001_en.html + # https://stat.fi/til/ekul/2010/ekul_2010_2011-12-13_tie_001_en.html series1 = QPieSeries() series1.setName("Fossil fuels") series1.append("Oil", 353295) diff --git a/examples/corelib/threads/mandelbrot.py b/examples/corelib/threads/mandelbrot.py index af53b343..c3b46d1d 100644 --- a/examples/corelib/threads/mandelbrot.py +++ b/examples/corelib/threads/mandelbrot.py @@ -264,22 +264,23 @@ class MandelbrotWidget(QWidget): self.thread.render(self._center_x, self._center_y, self._cur_scale, self.size()) def keyPressEvent(self, event): - if event.key() == Qt.Key.Key_Plus: - self.zoom(ZOOM_IN_FACTOR) - elif event.key() == Qt.Key.Key_Minus: - self.zoom(ZOOM_OUT_FACTOR) - elif event.key() == Qt.Key.Key_Left: - self.scroll(-SCROLL_STEP, 0) - elif event.key() == Qt.Key.Key_Right: - self.scroll(+SCROLL_STEP, 0) - elif event.key() == Qt.Key.Key_Down: - self.scroll(0, -SCROLL_STEP) - elif event.key() == Qt.Key.Key_Up: - self.scroll(0, +SCROLL_STEP) - elif event.key() == Qt.Key.Key_Q: - self.close() - else: - super(MandelbrotWidget, self).keyPressEvent(event) + match event.key(): + case Qt.Key.Key_Plus: + self.zoom(ZOOM_IN_FACTOR) + case Qt.Key.Key_Minus: + self.zoom(ZOOM_OUT_FACTOR) + case Qt.Key.Key_Left: + self.scroll(-SCROLL_STEP, 0) + case Qt.Key.Key_Right: + self.scroll(+SCROLL_STEP, 0) + case Qt.Key.Key_Down: + self.scroll(0, -SCROLL_STEP) + case Qt.Key.Key_Up: + self.scroll(0, +SCROLL_STEP) + case Qt.Key.Key_Q: + self.close() + case _: + super().keyPressEvent(event) def wheelEvent(self, event): num_degrees = event.angleDelta().y() / 8 diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml index 0fd26e4d..f90163c1 100644 --- a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml +++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogDelete.qml @@ -5,8 +5,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import QtExampleStyle - Popup { id: colorDeleter padding: 10 @@ -20,9 +18,9 @@ Popup { property string colorName: "" - function maybeDelete(color_id, name) { - colorName = name - colorId = color_id + function maybeDelete(data) { + colorName = data.name + colorId = data.id open() } @@ -31,17 +29,13 @@ Popup { anchors.fill: parent spacing: 10 - Text { - color: "#222222" + Label { text: qsTr("Delete Color?") - font.pixelSize: 16 font.bold: true } - Text { - color: "#222222" + Label { text: qsTr("Are you sure, you want to delete color") + " \"" + colorDeleter.colorName + "\"?" - font.pixelSize: 12 } RowLayout { @@ -58,9 +52,6 @@ Popup { Layout.fillWidth: true text: qsTr("Delete") - buttonColor: "#CC1414" - textColor: "#FFFFFF" - onClicked: { colorDeleter.deleteClicked(colorDeleter.colorId) colorDeleter.close() diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml index cba6e5a7..63bc8aee 100644 --- a/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml +++ b/examples/demos/colorpaletteclient/ColorPalette/ColorDialogEditor.qml @@ -6,8 +6,6 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs -import QtExampleStyle - Popup { id: colorEditor // Popup for adding or updating a color @@ -21,22 +19,24 @@ Popup { property bool newColor: true property int colorId: -1 - property alias currentColor: colordialogButton.buttonColor + property color currentColor: "white" function createNewColor() { newColor = true colorNameField.text = "cute green" - colorRGBField.text = "#41cd52" + currentColor = Qt.color("#41cd52") colorPantoneField.text = "PMS 802C" + colorDialog.selectedColor = currentColor open() } - function updateColor(color_id, name, color, pantone_value) { + function updateColor(data) { newColor = false - colorNameField.text = name - currentColor = color - colorPantoneField.text = pantone_value - colorId = color_id + colorNameField.text = data.name + currentColor = Qt.color(data.color) + colorPantoneField.text = data.pantone_value + colorId = data.id + colorDialog.selectedColor = currentColor open() } @@ -89,17 +89,25 @@ Popup { } } - Button { - id: colordialogButton + RowLayout { Layout.fillWidth: true - Layout.preferredHeight: 30 - text: qsTr("Set Color") - textColor: isColorDark(buttonColor) ? "#E6E6E6" : "#191919" + spacing: 10 - onClicked: colorDialog.open() + Rectangle { + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 + Layout.minimumWidth: 30 + Layout.minimumHeight: 30 + radius: 4 + border.width: 1 + border.color: palette.mid + color: colorEditor.currentColor + } - function isColorDark(color) { - return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) < 0.5; + Button { + Layout.fillWidth: true + text: qsTr("Change Color") + onClicked: colorDialog.open() } } @@ -117,9 +125,6 @@ Popup { Layout.fillWidth: true text: colorEditor.newColor ? qsTr("Add") : qsTr("Update") - buttonColor: "#2CDE85" - textColor: "#FFFFFF" - onClicked: { if (colorEditor.newColor) { colorEditor.colorAdded(colorNameField.text, diff --git a/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml index c6ad36f8..11639256 100644 --- a/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml +++ b/examples/demos/colorpaletteclient/ColorPalette/ColorView.qml @@ -9,14 +9,19 @@ import QtQuick.Layouts import QtQuick.Effects import QtQuick.Shapes -import QtExampleStyle -import ColorPalette - -Item { +Rectangle { id: root required property BasicLogin loginService - required property PaginatedColorsResource colors - required property PaginatedColorUsersResource colorViewUsers + required property PaginatedResource colors + required property PaginatedResource colorViewUsers + + color: root.palette.window + + function iconPath(baseImagePath) { + return Application.styleHints.colorScheme === Qt.ColorScheme.Dark + ? `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}_dark.svg` + : `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}.svg` + } ColorDialogEditor { id: colorPopup @@ -44,10 +49,10 @@ Item { ColumnLayout { // The main application layout anchors.fill :parent - + spacing: 0 ToolBar { Layout.fillWidth: true - Layout.minimumHeight: 25 + 4 + Layout.minimumHeight: 35 UserMenu { id: userMenu @@ -58,17 +63,82 @@ Item { RowLayout { anchors.fill: parent - Text { - text: qsTr("QHTTP Server") - font.pixelSize: 8 - color: "#667085" + anchors.leftMargin: 5 + anchors.rightMargin: 5 + + Button { + Layout.preferredWidth: 25 + Layout.preferredHeight: 25 + Layout.minimumWidth: 25 + Layout.minimumHeight: 25 + Layout.alignment: Qt.AlignVCenter + + display: AbstractButton.IconOnly + icon.source: root.iconPath("plus") + + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: qsTr("Add a new color") + + enabled: root.loginService.loggedIn + onClicked: colorPopup.createNewColor() } + + Button { + Layout.preferredWidth: 25 + Layout.preferredHeight: 25 + Layout.minimumWidth: 25 + Layout.minimumHeight: 25 + Layout.alignment: Qt.AlignVCenter + + display: AbstractButton.IconOnly + icon.source: root.iconPath("update") + + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: qsTr("Refresh colors and users") + + onClicked: { + root.colors.refreshCurrentPage() + root.colorViewUsers.refreshCurrentPage() + } + } + + Item { Layout.fillWidth: true } + + Image { + Layout.preferredWidth: 25 + Layout.preferredHeight: 25 + Layout.minimumWidth: 25 + Layout.minimumHeight: 25 + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + + source: "qrc:/qt/qml/ColorPalette/icons/qt.png" + fillMode: Image.PreserveAspectFit + } + + Label { + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + + text: qsTr("Color Palette") + font.bold: true + } + Item { Layout.fillWidth: true } AbstractButton { id: loginButton Layout.preferredWidth: 25 Layout.preferredHeight: 25 + Layout.minimumWidth: 25 + Layout.minimumHeight: 25 + + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: root.loginService.loggedIn + ? qsTr("Logged in as %1").arg(root.loginService.user) + : qsTr("Log in") + Item { id: userImageCliped anchors.left: parent.left @@ -83,9 +153,13 @@ Item { visible: false function getCurrentUserImage() { - if (root.loginService.loggedIn) - return users.avatarForEmail(loginService.user) - return "qrc:/qt/qml/ColorPalette/icons/user.svg"; + if (!root.loginService.loggedIn) + return root.iconPath("user"); + let users = root.colorViewUsers + for (let i = 0; i < users.data.length; i++) { + if (users.data[i].email === root.loginService.user) + return users.data[i].avatar; + } } } @@ -109,13 +183,14 @@ Item { userMenu.open() var pos = mapToGlobal(Qt.point(x, y)) pos = userMenu.parent.mapFromGlobal(pos) - userMenu.x = x - userMenu.width + 25 + 3 - userMenu.y = y + 25 + 3 + userMenu.x = x - userMenu.width + 50 + userMenu.y = y + 15 } Shape { id: bubble x: -text.width - 25 + y: -3 anchors.margins: 3 preferredRendererType: Shape.CurveRenderer @@ -124,7 +199,8 @@ Item { ShapePath { strokeWidth: 0 - fillColor: "#667085" + fillColor: root.palette.highlight + strokeColor: Qt.darker(root.palette.highlight, 1.3) startX: 5; startY: 0 PathLine { x: 5 + text.width + 6; y: 0 } PathArc { x: 10 + text.width + 6; y: 5; radiusX: 5; radiusY: 5} @@ -144,7 +220,7 @@ Item { x: 8 y: 8 id: text - color: "white" + color: root.palette.highlightedText text: qsTr("Log in to edit") font.bold: true horizontalAlignment: Qt.AlignHCenter @@ -152,83 +228,7 @@ Item { } } } - } - - Image { - anchors.centerIn: parent - source: "qrc:/qt/qml/ColorPalette/icons/qt.png" - fillMode: Image.PreserveAspectFit - height: 25 - } - - } - ToolBar { - Layout.fillWidth: true - Layout.minimumHeight: 32 - - RowLayout { - anchors.fill: parent - Text { - Layout.alignment: Qt.AlignVCenter - text: qsTr("Color Palette") - font.pixelSize: 14 - font.bold: true - color: "#667085" - } - - Item { Layout.fillWidth: true } - - AbstractButton { - Layout.preferredWidth: 25 - Layout.preferredHeight: 25 - Layout.alignment: Qt.AlignVCenter - - Rectangle { - anchors.fill: parent - radius: 4 - color: "#192CDE85" - border.color: "#DDE2E8" - border.width: 1 - } - - Image { - source: UIStyle.iconPath("plus") - fillMode: Image.PreserveAspectFit - anchors.fill: parent - sourceSize.width: width - sourceSize.height: height - } - visible: root.loginService.loggedIn - onClicked: colorPopup.createNewColor() - } - - AbstractButton { - Layout.preferredWidth: 25 - Layout.preferredHeight: 25 - Layout.alignment: Qt.AlignVCenter - - Rectangle { - anchors.fill: parent - radius: 4 - color: "#192CDE85" - border.color: "#DDE2E8" - border.width: 1 - } - - Image { - source: UIStyle.iconPath("update") - fillMode: Image.PreserveAspectFit - anchors.fill: parent - sourceSize.width: width - sourceSize.height: height - } - - onClicked: { - root.colors.refreshCurrentPage() - root.colorViewUsers.refreshCurrentPage() - } - } } } @@ -238,7 +238,7 @@ Item { ListView { id: colorListView - model: root.colors.model + model: root.colors.data //! [View and model] footerPositioning: ListView.OverlayFooter spacing: 15 @@ -250,37 +250,38 @@ Item { header: Rectangle { height: 32 width: parent.width - color: "#F0F1F3" + color: palette.window RowLayout { anchors.fill: parent - component HeaderText : Text { + component HeaderText : Label { Layout.alignment: Qt.AlignVCenter horizontalAlignment: Qt.AlignHCenter - - font.pixelSize: 12 - color: "#667085" } HeaderText { id: headerName text: qsTr("Color Name") - Layout.preferredWidth: colorListView.width * 0.3 + Layout.fillWidth: true + Layout.horizontalStretchFactor: 30 } HeaderText { id: headerRgb text: qsTr("Rgb Value") - Layout.preferredWidth: colorListView.width * 0.25 + Layout.fillWidth: true + Layout.horizontalStretchFactor: 25 } HeaderText { id: headerPantone text: qsTr("Pantone Value") - Layout.preferredWidth: colorListView.width * 0.25 + Layout.fillWidth: true + Layout.horizontalStretchFactor: 25 } HeaderText { id: headerAction text: qsTr("Action") - Layout.preferredWidth: colorListView.width * 0.2 + Layout.fillWidth: true + Layout.horizontalStretchFactor: 20 } } } @@ -288,13 +289,11 @@ Item { delegate: Item { id: colorInfo - required property int color_id - required property string name - required property string color - required property string pantone_value + required property var modelData width: colorListView.width - height: 25 + height: (colorListView.height - 55) / 6 - colorListView.spacing + // Header: 35, Footer 20, 55 together RowLayout { anchors.fill: parent anchors.leftMargin: 5 @@ -303,33 +302,37 @@ Item { Rectangle { id: colorSample Layout.alignment: Qt.AlignVCenter + Layout.minimumWidth: 36 + Layout.minimumHeight: 36 implicitWidth: 36 - implicitHeight: 21 + implicitHeight: 36 radius: 6 - color: colorInfo.color + color: colorInfo.modelData.color } - Text { + Label { Layout.preferredWidth: colorInfo.width * 0.3 - colorSample.width horizontalAlignment: Qt.AlignLeft leftPadding: 5 - text: colorInfo.name + text: colorInfo.modelData.name } - Text { + Label { Layout.preferredWidth: colorInfo.width * 0.25 horizontalAlignment: Qt.AlignHCenter - text: colorInfo.color + text: colorInfo.modelData.color } - Text { + Label { Layout.preferredWidth: colorInfo.width * 0.25 horizontalAlignment: Qt.AlignHCenter - text: colorInfo.pantone_value + text: colorInfo.modelData.pantone_value } Item { Layout.maximumHeight: 28 + Layout.minimumWidth: buttonBox.implicitWidth + Layout.minimumHeight: buttonBox.implicitHeight implicitHeight: buttonBox.implicitHeight implicitWidth: buttonBox.implicitWidth @@ -337,14 +340,20 @@ Item { id: buttonBox anchors.fill: parent ToolButton { - icon.source: UIStyle.iconPath("delete") + icon.source: root.iconPath("delete") + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: qsTr("Delete color") enabled: root.loginService.loggedIn - onClicked: colorDeletePopup.maybeDelete(color_id, name) + onClicked: colorDeletePopup.maybeDelete(colorInfo.modelData) } ToolButton { - icon.source: UIStyle.iconPath("edit") + icon.source: root.iconPath("edit") + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: qsTr("Edit color") enabled: root.loginService.loggedIn - onClicked: colorPopup.updateColor(color_id, name, color, pantone_value) + onClicked: colorPopup.updateColor(colorInfo.modelData) } } } @@ -371,6 +380,10 @@ Item { required property int index readonly property int page: (index + 1) + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: qsTr("Go to page %1").arg(page) + onClicked: root.colors.page = page } } diff --git a/examples/demos/colorpaletteclient/ColorPalette/Main.qml b/examples/demos/colorpaletteclient/ColorPalette/Main.qml index ae1e8553..e40192cb 100644 --- a/examples/demos/colorpaletteclient/ColorPalette/Main.qml +++ b/examples/demos/colorpaletteclient/ColorPalette/Main.qml @@ -4,13 +4,14 @@ pragma ComponentBehavior: Bound import QtQuick - -import ColorPalette +import QtQuick.Dialogs Window { id: window width: 500 height: 400 + minimumWidth: 500 + minimumHeight: 400 visible: true title: qsTr("Color Palette Client") @@ -19,44 +20,65 @@ Window { ColorView = 1 } - ServerSelection { - id: serverview - anchors.fill: parent - onServerSelected: {colorview.visible = true; serverview.visible = false} - colorResources: colors - restPalette: paletteService - colorUsers: users - } + // The Qt colorpalette REST API server listens here, so the client + // just connects on startup. + required property var serverUrl ColorView { id: colorview anchors.fill: parent - visible: false loginService: colorLogin - colors: colors + colors: colorPalette colorViewUsers: users } + MessageDialog { + id: connectionErrorDialog + title: qsTr("Connection failed") + text: qsTr("Could not reach the server at %1.").arg(window.serverUrl) + informativeText: qsTr("Start the Qt colorpalette REST API server " + + "on port 49425, then retry.") + buttons: MessageDialog.Cancel | MessageDialog.Retry + onButtonClicked: function(button) { + if (button === MessageDialog.Retry) { + colorPalette.refreshCurrentPage() + users.refreshCurrentPage() + } else if (button === MessageDialog.Cancel) { + Qt.quit() + } + } + } + + Connections { + target: colorPalette + function onErrorOccurred(message) { connectionErrorDialog.open() } + } + //! [RestService QML element] RestService { id: paletteService + url: window.serverUrl - PaginatedColorUsersResource { + PaginatedResource { id: users - path: "/api/users" + path: "users" } - PaginatedColorsResource { - id: colors - path: "/api/unknown" + PaginatedResource { + id: colorPalette + path: "unknown" } BasicLogin { id: colorLogin - loginPath: "/api/login" - logoutPath: "/api/logout" + loginPath: "login" + logoutPath: "logout" } } //! [RestService QML element] + Component.onCompleted: { + colorPalette.refreshCurrentPage() + users.refreshCurrentPage() + } } diff --git a/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml b/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml deleted file mode 100644 index c170773c..00000000 --- a/examples/demos/colorpaletteclient/ColorPalette/ServerSelection.qml +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import ColorPalette -import QtExampleStyle - -pragma ComponentBehavior: Bound - -Item { - id: root - // A popup for selecting the server URL - - signal serverSelected() - - required property PaginatedColorsResource colorResources - required property PaginatedColorUsersResource colorUsers - required property RestService restPalette - - Connections { - target: root.colorResources - // Closes the URL selection popup once we have received data successfully - function onDataUpdated() { - fetchTester.stop() - root.serverSelected() - } - } - - - ListModel { - id: server - ListElement { - title: qsTr("Public REST API Test Server") - url: "https://reqres.in" - icon: "qrc:/qt/qml/ColorPalette/icons/testserver.png" - } - ListElement { - title: qsTr("Qt-based REST API server") - url: "http://127.0.0.1:49425" - icon: "qrc:/qt/qml/ColorPalette/icons/qt.png" - } - } - - - ColumnLayout { - anchors.fill: parent - anchors.margins: 20 - spacing: 10 - - Image { - Layout.alignment: Qt.AlignHCenter - source: "qrc:/qt/qml/ColorPalette/icons/qt.png" - fillMode: Image.PreserveAspectFit - Layout.preferredWidth: 20 - } - - Label { - text: qsTr("Choose a server") - Layout.alignment: Qt.AlignHCenter - font.pixelSize: 24 - } - - component ServerListDelegate: Rectangle { - id: serverListDelegate - required property string title - required property string url - required property string icon - required property int index - - radius: 10 - color: "#00000000" - - border.color: ListView.view.currentIndex === index ? "#2CDE85" : "#E0E2E7" - border.width: 2 - - implicitWidth: 180 - implicitHeight: 100 - - Rectangle { - id: img - anchors.left: parent.left - anchors.top: parent.top - anchors.topMargin: 10 - anchors.leftMargin: 20 - - width: 30 - height: 30 - radius: 200 - border. color: "#E7F4EE" - border.width: 5 - - Image { - anchors.centerIn: parent - source: serverListDelegate.icon - width: 15 - height: 15 - fillMode: Image.PreserveAspectFit - smooth: true - } - } - - Text { - text: parent.url - - anchors.left: parent.left - anchors.top: img.bottom - anchors.topMargin: 10 - anchors.leftMargin: 20 - color: "#667085" - font.pixelSize: 13 - } - Text { - text: parent.title - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 10 - color: "#222222" - font.pixelSize: 11 - font.bold: true - } - - MouseArea { - anchors.fill: parent - onClicked: serverList.currentIndex = serverListDelegate.index; - } - } - - ListView { - id: serverList - Layout.alignment: Qt.AlignHCenter - Layout.minimumWidth: 180 * server.count + 20 - Layout.minimumHeight: 100 - orientation: ListView.Horizontal - - model: server - spacing: 20 - - delegate: ServerListDelegate {} - } - - Button { - Layout.alignment: Qt.AlignHCenter - text: restPalette.sslSupported ? qsTr("Connect (SSL)") : qsTr("Connect") - - buttonColor: "#2CDE85" - textColor: "#FFFFFF" - - onClicked: { - busyIndicatorPopup.title = (serverList.currentItem as ServerListDelegate).title - busyIndicatorPopup.icon = (serverList.currentItem as ServerListDelegate).icon - busyIndicatorPopup.open() - - fetchTester.test((serverList.currentItem as ServerListDelegate).url) - } - } - - Timer { - id: fetchTester - interval: 2000 - - function test(url) { - root.restPalette.url = url - root.colorResources.refreshCurrentPage() - root.colorUsers.refreshCurrentPage() - start() - } - onTriggered: busyIndicatorPopup.close() - } - } - - onVisibleChanged: {if (!visible) busyIndicatorPopup.close();} - - Popup { - id: busyIndicatorPopup - padding: 10 - modal: true - focus: true - anchors.centerIn: parent - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent - - property alias title: titleText.text - property alias icon: titleImg.source - - ColumnLayout { - id: fetchIndicator - anchors.fill: parent - - RowLayout { - Rectangle { - Layout.preferredWidth: 50 - Layout.preferredHeight: 50 - radius: 200 - border. color: "#E7F4EE" - border.width: 5 - - Image { - id: titleImg - anchors.centerIn: parent - width: 25 - height: 25 - fillMode: Image.PreserveAspectFit - } - } - - Label { - id: titleText - text:"" - font.pixelSize: 18 - } - } - - RowLayout { - Layout.fillWidth: false - Layout.alignment: Qt.AlignHCenter - BusyIndicator { - running: visible - Layout.fillWidth: true - } - - Label { - text: qsTr("Testing URL") - font.pixelSize: 18 - } - } - - Button { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Cancel") - onClicked: { - busyIndicatorPopup.close() - } - } - - } - - } -} diff --git a/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml index 6c4b2568..9dadc51a 100644 --- a/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml +++ b/examples/demos/colorpaletteclient/ColorPalette/UserMenu.qml @@ -8,45 +8,56 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Effects -import QtExampleStyle -import ColorPalette - Popup { id: userMenu required property BasicLogin userLoginService - required property PaginatedColorUsersResource userMenuUsers + required property PaginatedResource userMenuUsers width: 280 height: 270 - ColumnLayout { + background: Item {} + + function iconPath(baseImagePath) { + return Application.styleHints.colorScheme === Qt.ColorScheme.Dark + ? `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}_dark.svg` + : `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}.svg` + } + + Rectangle { + radius: 8 + border.width: 0 + color: palette.window + anchors.fill: parent ListView { id: userListView - - model: userMenu.userMenuUsers.model - spacing: 5 + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 5 + anchors.topMargin: 5 + anchors.bottomMargin: 2 + + model: userMenu.userMenuUsers.data + spacing: 7 footerPositioning: ListView.PullBackFooter clip: true Layout.fillHeight: true Layout.fillWidth: true - delegate: Rectangle { + delegate: Item { id: userInfo - required property string email - required property string avatar - height: 30 width: userListView.width + required property var modelData + readonly property bool logged: (modelData.email === userMenu.userLoginService.user) - readonly property bool logged: (email === loginService.user) - - Rectangle { + Item { id: userImageCliped anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter @@ -56,7 +67,7 @@ Popup { Image { id: userImage anchors.fill: parent - source: userInfo.avatar + source: userInfo.modelData.avatar visible: false } @@ -76,12 +87,12 @@ Popup { } } - Text { + Label { id: userMailLabel anchors.left: userImageCliped.right anchors.verticalCenter: parent.verticalCenter anchors.margins: 5 - text: userInfo.email + text: userInfo.modelData.email font.bold: userInfo.logged } @@ -90,18 +101,24 @@ Popup { anchors.verticalCenter: parent.verticalCenter anchors.margins: 5 - icon.source: UIStyle.iconPath(userInfo.logged + icon.source: userMenu.iconPath(userInfo.logged ? "logout" : "login") enabled: userInfo.logged || !userMenu.userLoginService.loggedIn + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: userInfo.logged + ? qsTr("Log out") + : qsTr("Log in as %1").arg(userInfo.modelData.email) + onClicked: { if (userInfo.logged) { userMenu.userLoginService.logout() } else { //! [Login] - userMenu.userLoginService.login({"email" : userInfo.email, + userMenu.userLoginService.login({"email" : userInfo.modelData.email, "password" : "apassword", - "id" : userInfo.id}) + "id" : userInfo.modelData.id}) //! [Login] userMenu.close() } @@ -129,6 +146,10 @@ Popup { required property int index readonly property int page: (index + 1) + ToolTip.visible: hovered + ToolTip.delay: 500 + ToolTip.text: qsTr("Go to page %1").arg(page) + onClicked: userMenu.userMenuUsers.page = page } } @@ -136,4 +157,13 @@ Popup { } } } + + Rectangle { + radius: 8 + border.color: palette.mid + border.width: 2 + color: "transparent" + + anchors.fill: parent + } } diff --git a/examples/demos/colorpaletteclient/ColorPalette/qmldir b/examples/demos/colorpaletteclient/ColorPalette/qmldir index 7a153fea..0ed4a540 100644 --- a/examples/demos/colorpaletteclient/ColorPalette/qmldir +++ b/examples/demos/colorpaletteclient/ColorPalette/qmldir @@ -3,5 +3,4 @@ Main 1.0 Main.qml ColorDialogDelete 1.0 ColorDialogDelete.qml ColorDialogEditor 1.0 ColorDialogEditor.qml ColorView 1.0 ColorView.qml -ServerSelection 1.0 ServerSelection.qml UserMenu 1.0 UserMenu.qml diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml deleted file mode 100644 index 6b3f922a..00000000 --- a/examples/demos/colorpaletteclient/QtExampleStyle/Button.qml +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.impl -import QtQuick.Templates as T - -T.Button { - id: control - - property alias buttonColor: rect.color - property alias textColor: label.color - - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding) - - leftPadding: 15 - rightPadding: 15 - topPadding: 10 - bottomPadding: 10 - - background: Rectangle { - id: rect - radius: 8 - border.color: "#E0E2E7" - border.width: 1 - color: "#FFFFFF" - } - - icon.width: 24 - icon.height: 24 - icon.color: control.palette.buttonText - - contentItem: IconLabel { - id: label - spacing: control.spacing - mirrored: control.mirrored - display: control.display - - icon: control.icon - text: control.text - font.pixelSize: 14 - color: "#667085" - } -} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt b/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt deleted file mode 100644 index a911f874..00000000 --- a/examples/demos/colorpaletteclient/QtExampleStyle/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(qtexamplestyle LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols/colorpaletteclient/QtExampleStyle") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick QuickControls2) - -set_source_files_properties(UIStyle.qml - PROPERTIES - QT_QML_SINGLETON_TYPE TRUE -) - -qt_policy(SET QTP0001 NEW) -qt_add_qml_module(qtexamplestyle - URI QtExampleStyle - PLUGIN_TARGET qtexamplestyle - QML_FILES - Button.qml - Popup.qml - UIStyle.qml - TextField.qml -) - -target_link_libraries(qtexamplestyle PUBLIC - Qt6::Core - Qt6::Gui - Qt6::Quick - Qt6::QuickControls2 -) - -if(UNIX AND NOT APPLE AND CMAKE_CROSSCOMPILING) - find_package(Qt6 REQUIRED COMPONENTS QuickTemplates2) - - # Work around QTBUG-86533 - target_link_libraries(qtexamplestyle PRIVATE Qt6::QuickTemplates2) -endif() - -install(TARGETS qtexamplestyle - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qmldir - DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml b/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml deleted file mode 100644 index a3132bce..00000000 --- a/examples/demos/colorpaletteclient/QtExampleStyle/Popup.qml +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Templates as T - -T.Popup { - id: control - - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - implicitContentHeight + topPadding + bottomPadding) - - leftPadding: 15 - rightPadding: 15 - topPadding: 10 - bottomPadding: 10 - - background: Rectangle { - id: bg - radius: 8 - border.color: "#E0E2E7" - border.width: 2 - color: "#FFFFFF" - } -} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml b/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml deleted file mode 100644 index 7db2d4f9..00000000 --- a/examples/demos/colorpaletteclient/QtExampleStyle/TextField.qml +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Templates as T - -T.TextField { - id: control - placeholderText: "" - - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding) - - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - radius: 8 - color: control.enabled ? "transparent" : "#353637" - border.color: "#E0E2E7" - } -} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml b/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml deleted file mode 100644 index 3c4741d7..00000000 --- a/examples/demos/colorpaletteclient/QtExampleStyle/UIStyle.qml +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -pragma Singleton - -import QtQuick - -QtObject { - id: uiStyle - - // Font Sizes - readonly property int fontSizeXXS: 10 - readonly property int fontSizeXS: 15 - readonly property int fontSizeS: 20 - readonly property int fontSizeM: 25 - readonly property int fontSizeL: 30 - readonly property int fontSizeXL: 35 - readonly property int fontSizeXXL: 40 - - // Color Scheme - // Green - readonly property color colorQtPrimGreen: "#41cd52" - readonly property color colorQtAuxGreen1: "#21be2b" - readonly property color colorQtAuxGreen2: "#17a81a" - - function iconPath(baseImagePath) { - return `qrc:/qt/qml/ColorPalette/icons/${baseImagePath}.svg` - } -} diff --git a/examples/demos/colorpaletteclient/QtExampleStyle/qmldir b/examples/demos/colorpaletteclient/QtExampleStyle/qmldir deleted file mode 100644 index 7bdfb44d..00000000 --- a/examples/demos/colorpaletteclient/QtExampleStyle/qmldir +++ /dev/null @@ -1,5 +0,0 @@ -module QtExampleStyle -Button 1.0 Button.qml -Popup 1.0 Popup.qml -TextField 1.0 TextField.qml -singleton UIStyle 1.0 UIStyle.qml diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.pyproject b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject index d05f7cb2..d02974ea 100644 --- a/examples/demos/colorpaletteclient/colorpaletteclient.pyproject +++ b/examples/demos/colorpaletteclient/colorpaletteclient.pyproject @@ -10,12 +10,8 @@ "ColorPalette/ColorDialogEditor.qml", "ColorPalette/ColorView.qml", "ColorPalette/Main.qml", - "ColorPalette/ServerSelection.qml", "ColorPalette/UserMenu.qml", - "QtExampleStyle/Button.qml", - "QtExampleStyle/Popup.qml", - "QtExampleStyle/TextField.qml", - "QtExampleStyle/UIStyle.qml", + "ColorPalette/qmldir", "colorpaletteclient.qrc" ] } diff --git a/examples/demos/colorpaletteclient/colorpaletteclient.qrc b/examples/demos/colorpaletteclient/colorpaletteclient.qrc index 16260cbd..34ebb5d2 100644 --- a/examples/demos/colorpaletteclient/colorpaletteclient.qrc +++ b/examples/demos/colorpaletteclient/colorpaletteclient.qrc @@ -1,17 +1,25 @@ icons/close.svg + icons/close_dark.svg icons/delete.svg + icons/delete_dark.svg icons/dots.svg icons/edit.svg + icons/edit_dark.svg icons/login.svg + icons/login_dark.svg icons/logout.svg + icons/logout_dark.svg icons/ok.svg + icons/ok_dark.svg icons/plus.svg + icons/plus_dark.svg icons/qt.png - icons/testserver.png icons/update.svg + icons/update_dark.svg icons/user.svg icons/userMask.svg + icons/user_dark.svg diff --git a/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst index 87e5016a..abf64563 100644 --- a/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst +++ b/examples/demos/colorpaletteclient/doc/colorpaletteclient.rst @@ -5,7 +5,7 @@ Example of how to create a RESTful API QML client. This example shows how to create a basic QML RESTful API client with an imaginary color palette service. The application uses RESTful communication -with the selected server to request and send data. The REST service is provided +with a local server to request and send data. The REST service is provided as a QML element whose child elements wrap the individual JSON data APIs provided by the server. @@ -14,37 +14,45 @@ Application functionality The example provides the following basic functionalities: -* Select the server to communicate with * List users and colors * Login and logout users * Modify and create new colors -Server selection +Running a server ---------------- -At start the application presents the options for the color palette server to communicate -with. The predefined options are: +The client talks to a local REST server on ``http://127.0.0.1:49425``. Start +one of the following before launching the client: -* ``https://reqres.in``, a publicly available REST API test service -* A `Qt-based REST API server C++ example`_ in the `QtHttpServer Module`_ +* A **FastAPI-based REST API server** bundled under ``server/`` — the + easiest option to get started. +* A `Qt-based REST API server C++ example`_ from the `QtHttpServer Module`_, + which listens on the same port by default — for developers who want to + test against the C++ backend. -Once selected, the RESTful API client issues a test HTTP GET to the color API -to check if the service is accessible. +Both servers expose the same REST API on the same port, so the client +connects to whichever one is running. To run the bundled FastAPI server: -One major difference between the two predefined API options is that the -Qt-based REST API server example is a stateful application which allows -modifying colors, whereas the ``reqres.in`` is a stateless API testing service. -In other words, when using the ``reqres.in`` backend, modifying the colors has -no lasting impact. +.. code-block:: bash + + cd server + pip install -r requirements.txt + ./start_server.sh + +or on Windows:: + + .\start_server.bat + +The FastAPI server is stateful: color additions, edits, and deletions persist +for the lifetime of the server process. + +The client connects on startup. If no server is reachable, it shows a dialog +reporting the failed connection; start a server and relaunch the client. The users and colors are paginated resources on the server-side. This means that the server provides the data in chunks called pages. The UI listing reflects this pagination and views the data on pages. -Viewing the data on UI is done with standard QML views where the model are -QAbstractListModel-derived classes representing JSON data received from the -server. - Logging in happens via the login function provided by the login popup. Under the hood the login sends a HTTP POST request. Upon receiving a successful response the authorization token is extracted from the response, which in turn @@ -56,13 +64,13 @@ changes to the server requires that a user has logged in. REST implementation ------------------- -The example illustrates one way to compose a REST service from individual resource elements. In -this example the resources are the paginated user and color resources plus the login service. -The resource elements are bound together by the base URL (server URL) and the shared network access -manager. +The example illustrates one way to compose a REST service from individual +resource elements. In this example the resources are the paginated user and +color resources plus the login service. The resource elements are bound +together by the base URL (server URL) and the shared network access manager. -The basis of the REST service is the RestService QML element whose children items -compose the actual service. +The basis of the REST service is the RestService QML element whose child +items compose the actual service. Upon instantiation the RestService element loops its children elements and sets them up to use the same network access manager. This way the individual @@ -75,7 +83,14 @@ effectively deals with sending and receiving the :class:`~PySide6.QtNetwork.QNetworkRequest` and :class:`~PySide6.QtNetwork.QNetworkReply` as needed. +Viewing the data on UI is done with standard `QML views`_ populated by +JSON data received from the server via the ``data`` property of the class +``PaginatedResource``. For C++ compatibility, it is declared to be of type +``QList``. It can be passed a list of dicts as obtained from +parsing using :class:`~PySide6.QtCore.QJsonDocument`. + .. image:: colorpaletteclient.webp + :width: 90% :align: center :alt: RESTful API client diff --git a/examples/demos/colorpaletteclient/icons/close.svg b/examples/demos/colorpaletteclient/icons/close.svg index 3a0d4be6..74a23e78 100644 --- a/examples/demos/colorpaletteclient/icons/close.svg +++ b/examples/demos/colorpaletteclient/icons/close.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/close_dark.svg b/examples/demos/colorpaletteclient/icons/close_dark.svg new file mode 100644 index 00000000..927e9e4c --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/close_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/delete.svg b/examples/demos/colorpaletteclient/icons/delete.svg index 8f04948c..55089c59 100644 --- a/examples/demos/colorpaletteclient/icons/delete.svg +++ b/examples/demos/colorpaletteclient/icons/delete.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/delete_dark.svg b/examples/demos/colorpaletteclient/icons/delete_dark.svg new file mode 100644 index 00000000..5ed1a9b4 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/delete_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/dots.svg b/examples/demos/colorpaletteclient/icons/dots.svg index 49df163f..c5bd5008 100644 --- a/examples/demos/colorpaletteclient/icons/dots.svg +++ b/examples/demos/colorpaletteclient/icons/dots.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/dots_dark.svg b/examples/demos/colorpaletteclient/icons/dots_dark.svg new file mode 100644 index 00000000..2c78e6ab --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/dots_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/edit.svg b/examples/demos/colorpaletteclient/icons/edit.svg index 1cfc2a73..db9c567b 100644 --- a/examples/demos/colorpaletteclient/icons/edit.svg +++ b/examples/demos/colorpaletteclient/icons/edit.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/edit_dark.svg b/examples/demos/colorpaletteclient/icons/edit_dark.svg new file mode 100644 index 00000000..8a2ea7a0 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/edit_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/login.svg b/examples/demos/colorpaletteclient/icons/login.svg index c8fe5bc5..12a93949 100644 --- a/examples/demos/colorpaletteclient/icons/login.svg +++ b/examples/demos/colorpaletteclient/icons/login.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/login_dark.svg b/examples/demos/colorpaletteclient/icons/login_dark.svg new file mode 100644 index 00000000..81f4ccd1 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/login_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/logout.svg b/examples/demos/colorpaletteclient/icons/logout.svg index 91d4fd86..7f28ca26 100644 --- a/examples/demos/colorpaletteclient/icons/logout.svg +++ b/examples/demos/colorpaletteclient/icons/logout.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/logout_dark.svg b/examples/demos/colorpaletteclient/icons/logout_dark.svg new file mode 100644 index 00000000..bf114531 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/logout_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/ok.svg b/examples/demos/colorpaletteclient/icons/ok.svg index 506e2d69..bbb2f4b8 100644 --- a/examples/demos/colorpaletteclient/icons/ok.svg +++ b/examples/demos/colorpaletteclient/icons/ok.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/ok_dark.svg b/examples/demos/colorpaletteclient/icons/ok_dark.svg new file mode 100644 index 00000000..4b19b742 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/ok_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/plus.svg b/examples/demos/colorpaletteclient/icons/plus.svg index 81837784..abbe8dbf 100644 --- a/examples/demos/colorpaletteclient/icons/plus.svg +++ b/examples/demos/colorpaletteclient/icons/plus.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/plus_dark.svg b/examples/demos/colorpaletteclient/icons/plus_dark.svg new file mode 100644 index 00000000..53914251 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/plus_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/testserver.png b/examples/demos/colorpaletteclient/icons/testserver.png deleted file mode 100644 index 0890e5e4..00000000 Binary files a/examples/demos/colorpaletteclient/icons/testserver.png and /dev/null differ diff --git a/examples/demos/colorpaletteclient/icons/update.svg b/examples/demos/colorpaletteclient/icons/update.svg index 303ff4d3..747a94b1 100644 --- a/examples/demos/colorpaletteclient/icons/update.svg +++ b/examples/demos/colorpaletteclient/icons/update.svg @@ -1,3 +1,3 @@ - + diff --git a/examples/demos/colorpaletteclient/icons/update_dark.svg b/examples/demos/colorpaletteclient/icons/update_dark.svg new file mode 100644 index 00000000..1980384e --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/update_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/colorpaletteclient/icons/user.svg b/examples/demos/colorpaletteclient/icons/user.svg index ed782385..e033318f 100644 --- a/examples/demos/colorpaletteclient/icons/user.svg +++ b/examples/demos/colorpaletteclient/icons/user.svg @@ -1,4 +1,4 @@ - - + + diff --git a/examples/demos/colorpaletteclient/icons/user_dark.svg b/examples/demos/colorpaletteclient/icons/user_dark.svg new file mode 100644 index 00000000..a00084b2 --- /dev/null +++ b/examples/demos/colorpaletteclient/icons/user_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/demos/colorpaletteclient/main.py b/examples/demos/colorpaletteclient/main.py index f32c1580..afbefc8c 100644 --- a/examples/demos/colorpaletteclient/main.py +++ b/examples/demos/colorpaletteclient/main.py @@ -6,11 +6,13 @@ from __future__ import annotations import os import sys +from argparse import ArgumentParser, RawDescriptionHelpFormatter from pathlib import Path from PySide6.QtCore import QUrl from PySide6.QtGui import QIcon, QGuiApplication from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtQuickControls2 import QQuickStyle from basiclogin import BasicLogin # noqa: F401 from paginatedresource import PaginatedResource # noqa: F401 @@ -18,10 +20,19 @@ from restservice import RestService # noqa: F401 import rc_colorpaletteclient # noqa: F401 if __name__ == "__main__": + parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter) + parser.add_argument("--url", type=str, help="The URL to open") + args = parser.parse_args() + url = args.url if args.url else "http://127.0.0.1:49425/api" + app = QGuiApplication(sys.argv) QIcon.setThemeName("colorpaletteclient") + QQuickStyle.setStyle("Fusion") + engine = QQmlApplicationEngine() + engine.setInitialProperties({"serverUrl": QUrl.fromUserInput(url)}) + app_dir = Path(__file__).parent app_dir_url = QUrl.fromLocalFile(os.fspath(app_dir)) engine.addImportPath(os.fspath(app_dir)) diff --git a/examples/demos/colorpaletteclient/paginatedresource.py b/examples/demos/colorpaletteclient/paginatedresource.py index 24be77fc..fdc3da08 100644 --- a/examples/demos/colorpaletteclient/paginatedresource.py +++ b/examples/demos/colorpaletteclient/paginatedresource.py @@ -3,10 +3,9 @@ from __future__ import annotations import sys -from dataclasses import dataclass -from PySide6.QtCore import (QAbstractListModel, QByteArray, - QUrlQuery, Property, Signal, Slot, Qt) -from PySide6.QtQml import QmlAnonymous, QmlElement +from PySide6.QtCore import (QUrlQuery, Property, Signal, Slot) +from PySide6.QtNetwork import QNetworkReply +from PySide6.QtQml import QmlElement from abstractresource import AbstractResource @@ -19,123 +18,7 @@ totalPagesField = "total_pages" currentPageField = "page" -@dataclass -class ColorUser: - id: int - email: str - avatar: str # URL - - @QmlElement -class ColorUserModel (QAbstractListModel): - IdRole = Qt.ItemDataRole.UserRole + 1 - EmailRole = Qt.ItemDataRole.UserRole + 2 - AvatarRole = Qt.ItemDataRole.UserRole + 3 - - def __init__(self, parent=None): - super().__init__(parent) - self._users = [] - - def clear(self): - self.set_data([]) - - def set_data(self, json_list): - if not self._users and not json_list: - return - self.beginResetModel() - self._users.clear() - for e in json_list: - self._users.append(ColorUser(int(e["id"]), e["email"], e["avatar"])) - self.endResetModel() - - def roleNames(self): - roles = { - ColorUserModel.IdRole: QByteArray(b'id'), - ColorUserModel.EmailRole: QByteArray(b'email'), - ColorUserModel.AvatarRole: QByteArray(b'avatar') - } - return roles - - def rowCount(self, index): - return len(self._users) - - def data(self, index, role): - if index.isValid(): - d = self._users[index.row()] - if role == ColorUserModel.IdRole: - return d.id - if role == ColorUserModel.EmailRole: - return d.email - if role == ColorUserModel.AvatarRole: - return d.avatar - return None - - def avatarForEmail(self, email): - for e in self._users: - if e.email == email: - return e.avatar - return "" - - -@dataclass -class Color: - id: int - color: str - name: str - pantone_value: str - - -@QmlElement -class ColorModel (QAbstractListModel): - IdRole = Qt.ItemDataRole.UserRole + 1 - ColorRole = Qt.ItemDataRole.UserRole + 2 - NameRole = Qt.ItemDataRole.UserRole + 3 - PantoneValueRole = Qt.ItemDataRole.UserRole + 4 - - def __init__(self, parent=None): - super().__init__(parent) - self._colors = [] - - def clear(self): - self.set_data([]) - - def set_data(self, json_list): - if not self._colors and not json_list: - return - self.beginResetModel() - self._colors.clear() - for e in json_list: - self._colors.append(Color(int(e["id"]), e["color"], - e["name"], e["pantone_value"])) - self.endResetModel() - - def roleNames(self): - roles = { - ColorModel.IdRole: QByteArray(b'color_id'), - ColorModel.ColorRole: QByteArray(b'color'), - ColorModel.NameRole: QByteArray(b'name'), - ColorModel.PantoneValueRole: QByteArray(b'pantone_value') - } - return roles - - def rowCount(self, index): - return len(self._colors) - - def data(self, index, role): - if index.isValid(): - d = self._colors[index.row()] - if role == ColorModel.IdRole: - return d.id - if role == ColorModel.ColorRole: - return d.color - if role == ColorModel.NameRole: - return d.name - if role == ColorModel.PantoneValueRole: - return d.pantone_value - return None - - -@QmlAnonymous class PaginatedResource(AbstractResource): """This class manages a simple paginated Crud resource, where the resource is a paginated list of JSON items.""" @@ -143,6 +26,7 @@ class PaginatedResource(AbstractResource): dataUpdated = Signal() pageUpdated = Signal() pagesUpdated = Signal() + errorOccurred = Signal(str) def __init__(self, parent=None): super().__init__(parent) @@ -151,12 +35,7 @@ class PaginatedResource(AbstractResource): # The default page we request if the user hasn't set otherwise self.m_currentPage = 1 self.m_path = "" - - def _clearModel(self): - pass - - def _populateModel(self, json_list): - pass + self._data = [] @Property(str) def path(self): @@ -198,12 +77,12 @@ class PaginatedResource(AbstractResource): else: error = jsonError.errorString() else: - reply_error = reply.errorString() - error = reply_error if reply_error else "Network error" - + error = (reply.errorString() if reply.error() != QNetworkReply.NetworkError.NoError + else f"HTTP status: {reply.httpStatus()}") if error: url = reply.networkReply().url().toString() print(f'PaginatedResource: request "{url}" failed: "{error}"', file=sys.stderr) + self.errorOccurred.emit(error) self.refreshRequestFailed() def refreshRequestFinished(self, json): @@ -211,7 +90,7 @@ class PaginatedResource(AbstractResource): data = json_object.get("data") totalPages = json_object.get(totalPagesField) currentPage = json_object.get(currentPageField) - self._populateModel(data if data else []) + self._data = data if data else [] self.m_pages = int(totalPages) if totalPages else 1 self.m_currentPage = int(currentPage) if currentPage else 1 self.pageUpdated.emit() @@ -227,7 +106,7 @@ class PaginatedResource(AbstractResource): # Refresh failed and we we're already on page 1 => clear data self.m_pages = 0 self.pagesUpdated.emit() - self._clearModel() + self._data = [] self.dataUpdated.emit() @Slot("QVariantMap", int) @@ -249,42 +128,6 @@ class PaginatedResource(AbstractResource): request = self.m_api.createRequest(f"{self.m_path}/{id}") self.m_manager.deleteResource(request, self, self.updateReply) - -@QmlElement -class PaginatedColorUsersResource(PaginatedResource): - - def __init__(self, parent=None): - super().__init__(parent) - self.m_model = ColorUserModel(self) - - @Property(ColorUserModel, constant=True) - def model(self): - return self.m_model - - def _clearModel(self): - self.m_model.clear() - - def _populateModel(self, json_list): - self.m_model.set_data(json_list) - - @Slot(str, result=str) - def avatarForEmail(self, email): - return self.m_model.avatarForEmail(email) - - -@QmlElement -class PaginatedColorsResource(PaginatedResource): - - def __init__(self, parent=None): - super().__init__(parent) - self.m_model = ColorModel(self) - - @Property(ColorModel, constant=True) - def model(self): - return self.m_model - - def _clearModel(self): - self.m_model.clear() - - def _populateModel(self, json_list): - self.m_model.set_data(json_list) + @Property("QList", notify=dataUpdated, final=True) + def data(self): + return self._data diff --git a/examples/demos/colorpaletteclient/rc_colorpaletteclient.py b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py index 74b3eaf1..ce54185f 100644 --- a/examples/demos/colorpaletteclient/rc_colorpaletteclient.py +++ b/examples/demos/colorpaletteclient/rc_colorpaletteclient.py @@ -1,6 +1,6 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.7.0 +# Created by: The Resource Compiler for Qt version 6.11.1 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore @@ -18,9 +18,9 @@ path d=\x22M22.5 38\ V25.5H10V22.5H22\ .5V10H25.5V22.5H\ 38V25.5H25.5V38H\ -22.5Z\x22 fill=\x22#66\ -7085\x22/>\x0a\x0a\ -\x00\x00\x00\x94\ +22.5Z\x22 fill=\x22#19\ +1919\x22/>\x0a\x0a\ +\x00\x00\x00\x93\ <\ svg width=\x2230\x22 h\ eight=\x2230\x22 viewB\ @@ -31,7 +31,56 @@ mlns=\x22http://www\ 15\x22 cy=\x2215\x22 rx=\x22\ 13\x22 ry=\x2213\x22 fill\ =\x22black\x22/>\x0a\x0a\x0a\ +>\x0a\ +\x00\x00\x02\xed\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M24 40C1\ +9.5667 40 15.791\ +7 38.4417 12.675\ + 35.325C9.55833 \ +32.2083 8 28.433\ +3 8 24C8 19.5667\ + 9.55833 15.7917\ + 12.675 12.675C1\ +5.7917 9.55833 1\ +9.5667 8 24 8C26\ +.8333 8 29.3167 \ +8.575 31.45 9.72\ +5C33.5833 10.875\ + 35.4333 12.45 3\ +7 14.45V8H40V20.\ +7H27.3V17.7H35.7\ +C34.4333 15.7 32\ +.8167 14.0833 30\ +.85 12.85C28.883\ +3 11.6167 26.6 1\ +1 24 11C20.3667 \ +11 17.2917 12.25\ +83 14.775 14.775\ +C12.2583 17.2917\ + 11 20.3667 11 2\ +4C11 27.6333 12.\ +2583 30.7083 14.\ +775 33.225C17.29\ +17 35.7417 20.36\ +67 37 24 37C26.7\ +667 37 29.3 36.2\ +083 31.6 34.625C\ +33.9 33.0417 35.\ +5 30.95 36.4 28.\ +35H39.5C38.5333 \ +31.85 36.6167 34\ +.6667 33.75 36.8\ +C30.8833 38.9333\ + 27.6333 40 24 4\ +0Z\x22 fill=\x22#E6E6E\ +6\x22/>\x0a\x0a\ \x00\x00\x0b\x93\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -220,6 +269,144 @@ U\xf3\x90\xd4\x05\x03\xe2(\xa8%\xdd-\xd6e\x18\x92\ \x1f\x03\x0f\x9b-\xf5S\xcd{\xc8\xfe\xb7\xfd\x07:\xcc\ \xccF\x8ay\xc7t\x00\x00\x00\x00IEND\xaeB\ `\x82\ +\x00\x00\x01V\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M9 42C8.\ +2 42 7.5 41.7 6.\ +9 41.1C6.3 40.5 \ +6 39.8 6 39V9C6 \ +8.2 6.3 7.5 6.9 \ +6.9C7.5 6.3 8.2 \ +6 9 6H23.55V9H9V\ +39H23.55V42H9ZM3\ +3.3 32.75L31.15 \ +30.6L36.25 25.5H\ +18.75V22.5H36.15\ +L31.05 17.4L33.2\ + 15.25L42 24.05L\ +33.3 32.75Z\x22 fil\ +l=\x22#E6E6E6\x22/>\x0a\x0a\ +\x00\x00\x00\xdf\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M18.9002\ + 35.7L7.7002 24.\ +5L9.8502 22.35L1\ +8.9002 31.4L38.1\ +002 12.2L40.2502\ + 14.35L18.9002 3\ +5.7Z\x22 fill=\x22#E6E\ +6E6\x22/>\x0a\x0a\ +\x00\x00\x036\ +<\ +svg width=\x2224\x22 h\ +eight=\x2224\x22 viewB\ +ox=\x220 0 24 24\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M0 12C0 \ +5.37258 5.37258 \ +0 12 0C18.6274 0\ + 24 5.37258 24 1\ +2C24 18.6274 18.\ +6274 24 12 24C5.\ +37258 24 0 18.62\ +74 0 12Z\x22 fill=\x22\ +#E6E6E6\x22/>\x0a\x0a\x0a\ +\x00\x00\x01\xb3\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M13.05 4\ +2C12.225 42 11.5\ +187 41.7062 10.9\ +313 41.1188C10.3\ +438 40.5312 10.0\ +5 39.825 10.05 3\ +9V10.5H8V7.5H17.\ +4V6H30.6V7.5H40V\ +10.5H37.95V39C37\ +.95 39.8 37.65 4\ +0.5 37.05 41.1C3\ +6.45 41.7 35.75 \ +42 34.95 42H13.0\ +5ZM34.95 10.5H13\ +.05V39H34.95V10.\ +5ZM18.35 34.7H21\ +.35V14.75H18.35V\ +34.7ZM26.65 34.7\ +H29.65V14.75H26.\ +65V34.7Z\x22 fill=\x22\ +#E6E6E6\x22/>\x0a\x0a\ +\x00\x00\x00\xc0\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M22.5 38\ +V25.5H10V22.5H22\ +.5V10H25.5V22.5H\ +38V25.5H25.5V38H\ +22.5Z\x22 fill=\x22#E6\ +E6E6\x22/>\x0a\x0a\ \x00\x00\x02\xed\ <\ svg width=\x2248\x22 h\ @@ -267,8 +454,8 @@ C12.2583 17.2917\ .6667 33.75 36.8\ C30.8833 38.9333\ 27.6333 40 24 4\ -0Z\x22 fill=\x22#66708\ -5\x22/>\x0a\x0a\ +0Z\x22 fill=\x22#19191\ +9\x22/>\x0a\x0a\ \x00\x00\x01\xb3\ <\ svg width=\x2248\x22 h\ @@ -297,9 +484,9 @@ path d=\x22M13.05 4\ 34.7ZM26.65 34.7\ H29.65V14.75H26.\ 65V34.7Z\x22 fill=\x22\ -#667085\x22/>\x0a\x0a\x0a\ -\x00\x00\x01\xf7\ +\x00\x00\x01\xf6\ <\ svg width=\x2248\x22 h\ eight=\x2248\x22 viewB\ @@ -331,436 +518,30 @@ V35.6L31.2 10.4L\ 15.75L31.15 14.\ 65L33.35 16.85L3\ 2.25 15.75Z\x22 fil\ -l=\x22#667085\x22/>\x0a\x0a\x0a\ -\x00\x00\x1a\x93\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00J\x00\x00\x00H\x08\x06\x00\x00\x00Q\x18cz\ -\x00\x00\x01\x85iCCPICC prof\ -ile\x00\x00(\x91}\x91=H\xc3@\x18\x86\xdf\ -\xa6\x8a\x22-\x0ev\x10q\xc8P\x1d\xa4\x05Q\x11G\ -\xadB\x11*\x84Z\xa1U\x07\x93K\xff\xa0IC\x92\ -\xe2\xe2(\xb8\x16\x1c\xfcY\xac:\xb88\xeb\xea\xe0*\ -\x08\x82? \xae.N\x8a.R\xe2wI\xa1E\x8c\ -w\x1c\xf7\xf0\xde\xf7\xbe\xdc}\x07\x08\x8d\x0a\xd3\xac\xae\ -q@\xd3m3\x9dL\x88\xd9\xdc\xaa\xd8\xf3\x8a\x10\xc2\ -4\xc7\x10\x93\x99e\xccIR\x0a\xbe\xe3\xeb\x1e\x01\xbe\ -\xdf\xc5y\x96\x7f\xdd\x9f#\xac\xe6-\x06\x04D\xe2Y\ -f\x986\xf1\x06\xf1\xf4\xa6mp\xde'\x8e\xb0\x92\xac\ -\x12\x9f\x13\xc7L\xba \xf1#\xd7\x15\x8f\xdf8\x17]\ -\x16xf\xc4\xcc\xa4\xe7\x89#\xc4b\xb1\x83\x95\x0ef\ -%S#\x9e\x22\x8e\xaa\x9aN\xf9B\xd6c\x95\xf3\x16\ -g\xadRc\xad{\xf2\x17\x86\xf2\xfa\xca2\xd7i\x0d\ -#\x89E,A\x82\x08\x055\x94Q\x81\x8d8\xed:\ -)\x16\xd2t\x9e\xf0\xf1\x0f\xb9~\x89\x5c\x0a\xb9\xca`\ -\xe4X@\x15\x1ad\xd7\x0f\xfe\x07\xbf{k\x15&'\ -\xbc\xa4P\x02\xe8~q\x9c\x8f\x11\xa0g\x17h\xd6\x1d\ -\xe7\xfb\xd8q\x9a'@\xf0\x19\xb8\xd2\xdb\xfej\x03\x98\ -\xf9$\xbd\xde\xd6\xa2G@\xff6pq\xdd\xd6\x94=\ -\xe0r\x07\x18|2dSv\xa5 -\xa1P\x00\xde\ -\xcf\xe8\x9br\xc0\xc0-\xd0\xb7\xe6\xf5\xadu\x8e\xd3\x07\ - C\xbdJ\xdd\x00\x07\x87\xc0h\x91\xb2\xd7}\xde\xdd\ -\xdb\xd9\xb7\x7fkZ\xfd\xfb\x01\xa7vr\xbc\xf6x\x14\ -.\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\ -\xbd\xa7\x93\x00\x00\x00\x09pHYs\x00\x00.#\x00\ -\x00.#\x01x\xa5?v\x00\x00\x00\x07tIME\ -\x07\xe7\x0c\x0d\x0d+\x19\xea\xccr\xbc\x00\x00\x00\x19t\ -EXtComment\x00Creat\ -ed with GIMPW\x81\x0e\x17\ -\x00\x00\x18jIDATx\xda\xed[y`T\xd5\ -\xd5\xff\xdd\xfb\xde\xac\x99}\xb2/da\x93E@P\ -\xb1\xdaZZ\xfd\x94-\xc8\xe2\x04\x14\xb7JK\xe5\xf3\ -\x0b&\x80\x88\xd5\xb6Q\x8b~\x22\x90\x80b-\xa2b\ -QI\x18\x11H \x91\x8a\x15k\xddQPY\x0ck\ -\x12\xb2'\x93\x99\xc9\xccd\x96\xf7\xde\xbd\xdf\x1f\x81@\ -\x08\x89Qh\xc1\xaf\x9c?g\xee\xbcw\xde\xef\x9d\xfb\ -;\xe7\xfc\xce\x1d\xe0\x92]\xb2Kv\xc9.Y\xaf\xcd\ -\x91\xb7O\xedpl\x10.6\xbf\xc8\xc5\xe2\xc8\xf4\xe7\ -\xf8H@~\x82s\x0c\x03\xe1!\x0a\xfa1\x04\xe9\x0f\ -\x85st\x15\x97\x80\x02\xe0X\xe2\x8f\xa7:\xed\xef\x08\ -\xf8o8\xa0=\xe3\xebVp\xaca\xb2\xb8\xd89\x8f\ -\xb4\xfcG\x02\xe5\xd8\xc0\x05\xda \xfd\x0f\x07\xfd=\x01\ -\xb7\xf7\xec%\xa9\x00g\x8f\x17e\xab_\xf9\x8f\x02j\ -\xc6Jy\x02'\xfc\x7f\x01\x0c\xfd>\xbf\xe3\xe0\xdf\x10\ -\x8aYE\xf7\xab?\xff\x7f\x0d\xd4\xa4\xa7\x03\x89:\xbd\ -\xfae\x007\x9f\xcbu8\xe7\xaf\x8bT\xf5\xc8\x1b\xff\ -C*\xff_\x015\xe9in\xd4F\xc9\x8f\x10\x86\x5c\ -\x10\xa8\xbb[\xa7\x84$\xb8\x0f\xd6B\xd0\xa9aJ\x8d\ -\x86\xa0V\xf5t\xd9V\x0e\xb6\xbc\xd1\xa5^\xbc3\x8f\ -\xc8?z\xa0\xb2V\x86o#Dx\x0e\xe0\xb6n#\ -Da\xf0V6\xc1_\xed\x02g\x1c\x00@U\x14\xa6\ -\x94\x18\x18R\xec \x84\xf4\xc8_DQ\xe6\x15>\xa0\ -\xd9\xf4\xa3\x04\xea\xd6\x95\xd2\xcfD\x82\xa5\x1c\xb8\xba\x87\ --\x84`S+\xdc\x07\xeb\xc0d\xe5\xackD\xad\x1a\ -\x96~q\xd0E\x9b\xbekC~\xaa\x12Tw\xbf\xf6\ -\xdf\xa4\xfcG\x01\xd4\xac5\xdc\xe6o\x93\x0a@\xc8\x9d\ -=\xad\x0b{\xdb\xe0>T\x0f\xc9\x1f\xec\xd5u5\x96\ -(\xd8.K\x84\xa8U\x7f\x17\xe1\xbf\xc0\x15\xd5S\xce\ -\x1cRuQ\x025s%7)T\x9e\xc3\x81\x05\xe0\ -\x88\xee\x96\x87\x22\x12Z+\x9b\xe1\xaf\xf9ae\x911\ -\xd9\x0eC\xb2\x1d\xa2\xb6g\xfe\x22\x9c,PY\x847\ -\xd6\xddE\x02\x17\x0dP\x8e\x15\xd2X\x22`\x09\xe1\xb8\ -\xbc\xdb7-3\xf8j\x5ch\xadl\xea\xe0\xa1\x1fj\ -\x82F\x05sZ,\xa2\xe2-=?\x01A9!\xe4\ -\xd1\xc2\xfb\xc57/8P\xb3\xd7*s\xbd>\xb6\xa2\ -\xa75A\x97\x0f\xde\xa3\x8d\x90\x02\xa1\xf3\xba\xcdU\x06\ --,\x19q\xd0\xda\x0c=p=`\x8c\xa2\x8b^\xfc\ -\x95\xf0\xf4\xb9\xdc\x8b\x9e\xab\xb3\xdc\xef\xbf\x22%^\x80\ -^wv\xcc\x99\xac\xa0\xad\xa9\xf5\xbc\x83\x04\x00\x92?\ -\x04_\x8d\x0b\xbc\x9bD\xa0\xd3\x12$\xc4P\xc0\xe7\x1b\ -p\xae\xf7\x12\xcf\xf5\x02\xfb\xfe~\x14q\xe96)\xed\ -\x8ax\xc1\xa0\x17\xa9\xdb\xc3 \x9d\xe67\x15\x05\xd8/\ -K\x82!\xc1\x0a\xcf\x91zDZ\x83\xe7'\x9a\xa2\xb4\ -\xb0\xf4\x8b\x87\xd6\x1a\xd5\xf5\xa1\x04\xc0f\xa6\xa0R\x84\ -\x1d\xfe\xb0\x96\xb9\xaa[\xc9\x05\x07\x0a h\xa8p\xab\ -\x9a\xab[\xa5\xb8t\x0bRG$ (\x11\xea\xf2\xb0\ -\xceY\xcb\xacG\xdc\xc8\x0c\x04\xea=\xf0\x1ci\x00\x93\ -~X\x8dHE\x0aC\xb2\x1d\xa6\x94h\x10\x81v\xe1\ -\x11\xb3\x91\xc2\xa8\xe7J\xcd\xfe&\xd4|\xdbDeI\ -\x11A\x08\xbf\xe0@q\xc2\x09\x01\x81\x22+\xaa\xdaC\ -.\xb8j[\x91:4NJ\xedkS\xb5x\x19|\ -\x81\xce>F\xc5[\xa0\xb5F\xc1_\xebFke\xd3\ -\xf7\xcbxIv\x18S\xa3!\xa8\xbb\xba\x1d\xa5#\xb0\ -[(\x9a\x8e\xb5(\x07\xf6\xd4\x139$\xd3\x19\xd7\xd7\ -`o\xa5\x11{+M\xb8\x08\x22\xea\x8c\xfa( \xe1\ -\xe0\xa7\xd5\xaa\xe3\x07\x9a\xe4!?M\x859^+6\ -63Dd\xde9k\xa5\xc7\x22*\xc1\x0a\xf7\xc1:\ -\x84Z|=^Sm\xd2\xc1\xda/\x01j\x93\xae\xeb\ -\x03\x88@\x9cM\x80\x1c\x0c\xc9{\xdf\xad\x15\xbc\x8d~\ -\xe1Z\xa1\x12aQ\xc4\xc8\x0c/v~c\x07\x01\xf8\ -E\x00\x14\xc5\xd9\xfc\x08\xb6\x86\xc5]\xa5\x07\x11\x93j\ -\x8e\xf4\x1b\x95\xa8\x0eC\x84\xdb\xcbp:\xef\x8aZ\x15\ -b\x86\xf5A\xd8\xdb\x86\x96\xf2Z\xc8m\xe1\xceWV\ -\x09\xb0\x0dL\x82\xd6n\xe8\xd2\xc6\x10\x02D[)t\ -*\x8e\xf2\x8f\xab\xe4\xe6*\x8fh\x89mC\xd2\xc00\ -\xa6T\xee\xc7\xb2\xc8O\xb1pmR\xfbZ\x8e\x8b/\ -\xa2\xce\xb4\xa6J\xaf\xba\xb9\xaa\x15\xa9\xc3b\x918(\ -\x16\xfe \xe0\xf6v\xe5\xaf\x84\xab\xfa\xc1_\xe3\x82\xb7\ -\xaa\x19\x5cV`H\xb1\xc3\x9c\x1a\x03Bi\xd7t\xaf\ -\xa7\xb0Y\x08\xea\xca]\xf8\xfa\xabz\xc8\x92\x22\x02\xc0\ -\xe8[\x8e\xe2\xc0G\x09x04\xaes\xe6\x05\xbb\xf8\ -\x81:\xd9\xd3U|\xd5\x80\xfa\xc3nd\x8cL@j\ -\x8a\x19\xcdn\x86@\x90wbbC\xb2\x1d\xfa8\x0b\ -\x00\x0e\xaa\xea\xea\x9aVC\x10k\xa3\x084\x07\xf0\xd9\ -\xa6*\x84\xdb\xa4N\xdf\xbf\xf3\xca`0\x99\x9e\xa5\x96\ -\x12\xd8\x85\x07J\x11\x9f\xe7\x824\x86\x00\xa9\xdf\xb54\ -\x14\x88`\xff\x07\x95\x88\xb2j1\xf8\xfa4XLj\ -4\xba\x14\x9c\x9e\x00\xa9J8k\xba\xb7[(\x88,\ -\xe1\xdb\x0f\xea\xe0\xaa\xf6vS\xb3\x9d\xb5,t\x83\x93\ -\xd7.\x8a\x16\xe6g\xd9\x87b\xd4\x11\xfaN\xc8\x13\x18\ -\xce\xe5\xde\xbf\xbc\xf8~v\xa4\x0f\x8bE\x88\x8b\xf0\xb6\ -v\xae\xbf\x00\x80\x12\xc0j\xa6\xd0\xab9\xaa\xbei@\ -Mys\xaf\xdb\x1f\x22P\xe8l\x86]A\x8f\xe7\x96\ -\x0f\x0bG\xd6^P\xa0&M\xcd\xb9\x8ed\xdc`\xd3\ -\xa5\x8e]\xc6\x81\xferH\x82\xf7h\x03\xda\x1a\xbd\xbd\ -w\x80\x12\xf4\xbd2\x11q\x196x\xfd\x1c\xa1\x08\xc0\ -\x19\xa0\xd5\x00V\x13Ec\x85\x1b\xc7\xf6\xd4A\x0a\xf6\ -\xbe\xee\xd2Z\xa3`\x1d\x98t\xa2q\xe6\xfb\xa5\xa3\xa5\ -\x8fJ\xc7w\x86J\xde,(\xfb\xc1\xfd\xe5\xb9\x005\ -h\xf4X_\xca\x15\xe3WP\x8dqTDj\xaf\xc2\ -\xf51&hLzH\xbe\x10\x98\xa4\xf4\x82\xc0\x80\x96\ -\x1a\x1f\x9a+\xbd0[T\xb0YE\x18t\x80\xec\x0f\ -\xe2\xdb\x0f\xabPw\xd0\x05v\x22J\x09%P\xa9E\ -0\x85u_F\xf4O\x809#\x0eTl\x7f4\x83\ -\x9e\xc6\x98-\xa6\x18_\xf9\xae%\xe5\xe5\x1fF.\xd8\ -\xd6\xbb\xf9\xd7\xdf\xbe\x9c:P\x04\x15\x5c\xee\ -\xce\xfd^\xa7lC\xdb\xfb<\x15\x14\x1c\xdb]\x87\x86\ -c\xee\xefQ\x93\x85\xa1Ay\xb9\x06_\xe6\x96\xbdY\ -P\x06p\x02\x10\x9e9}\xc1-\xe0\xca\x03\x9c\xa0B\ -\x11\xcd9e\xaf\xe7\xb5\x02\x80\xc3\x91\x1d\xd3F\xc4\xf9\ -\x04\x90h\xc8\xbb\xe4\xf4\xc8#\xdd\x91\xb5\xc00\x8fp\ -ZQw\xd4\xff\xec\xe9)t\xf2\xe4\x1c\x8b\xac\xa1\x0f\ -\x83\xf3\x0cF\xe8\xf6\xd2\xc2\xa5/e=\x17\x99\xc9\x82\ -\x81\xb5\xad\xb5~\xc1_\xe3\x06\xbe\xc78*iP4\ -\xfa\x0c\x8aAP\x11\xe1\xf6\x9d\xe2/B\x00\x8b\x91\xc2\ -\xa8\xe5\xa8\xda\xd7\x88\x9a\xf2\xe6^e\xca\x93\x15|T\ -\x82\x0d\x86D\xbdG\xd4\xeb\xe7\x1fy\xf5\x81uI\xe9\ -Q\xd3\x18\xe5\xb7\x81\xd2}\xac5\xb4\xb2\xb4\xf4T\xb2\ -\x1a\x959[\x9f\xa0\x8f\x9a\x0b\xf0\xc1r\x18\x7fx\xfb\ -D\x80\xf4\xba2\x9f8c\xee\x0dP\xc4\x1c\x85\xf0\xd5\ -\xa3\x07\x9b\xb7}\xbe\xdf{'%\xb8\x81\x81?\xb5\xad\ -\xa8\xe0@fV\xcem\xa6A\x93\x17(1\xd7\x8d\xe4\ -'\xb0\x91C\x11\xb8\x0f\xd5!\xe4\xf2\xf7^\xc2P\x0b\ -\xe8;*\x11\xf1}m\x08\x8698\x07t\x1a\xc0]\ -\xef\xc7\xc1\x8f\xab\x11n\xeb}\xe7\xa1\xb1\xe8a\x1b\x90\ -\x08Q\xaf\xe9h\xac\xa9\xeb\xa3\xc3\xad\x07\x8a\x1f/)\ -\x5c\xb6n\xe2\xb4\x07\xfbC\x94\xf38\xc7\xdf\xb6m\xc8\ -\x7fuR\xd6\xbc_0\xce\xfe\x9b\x13a\xcd\xb6\x0d\xcb\ -\xb6\xff\xe0\x16&//\x8f\xee\xda\xef\x99\x03\x90\xe1\xe0\ -\xec\xdd\xad\xce\x15E\x9d\xe6z\xcfz^\xb6$\x9a~\ -\xe5\xf20\xf8\xdbNER[c+\xbcG\x1b \x87\ -z\xff\x90z\x93\x16\xd6D#\x08\x01\xbcM\x01\xf8\x9a\ -\xdb\xce\xd8\xaez\x08j\x01-5]\x8bV\x95N\x03\ -Sz\x0c\xf4\xb1\xe6S\xd7\xd3\x12\xd8\xcc\x94\xb5\xd6\xb5\ -\xbe\xf2\xe2\x03\x96_\x9f\xbe~|Vn\x16!\xb8\x9d\ -3|X\xea\xcc\x7f\xe6_\xde\xeb];u\xf7+\xe6\ -X\xc3\xed\xfd\xaeNV\x04\xbdF\xe7\xf2(\x88H'\ -\xeb%\x0e\x7fm\x0b|\xc7\x9b\xa1\x84\x7f\xf8\xc9\x9c(\ -\x8b\x16\x1a\xbd\x0a\xb6$#B\xfeH\xa7^\x8f\x88\x14\ -\xa6d;\x8c\xa91\x1dm\x92Z\x0d\xd8\xcd\x14\x92\xaf\ -\x8d\x1f\xdeU\xc7}M\x81W?z\xeb\x8a{/\xf8\ -\xb8\xca\xdb\x14P\x7fQz\x90\xc5gXy\xea\xb0X\ -\x22\x11\x15<^\x06\x09\x04\xc6d;t\xd1&\xb4V\ -4\xa2\xad\xc1\x0b\xce{\xcf_*\xad\x08\xbdI\x03[\ -\x92\x11L\xe1\xa7\x9a\xe6\x93\x11\x13k\x86)=\x16*\ -\x9d\xba\x83\xd7lf\x0a\xad\xa0\xb0\xea\xbd\xf5\xbc\xf6p\ -\x0ba\x92B/\xaaq\x158\xa7\xf5GZ\xe0\xae\xf5\ -)\x89\x03\xa3\x95\xf8\x016Uk\x1b!\xbe\x00\x87\xa8\ -U\xc1vY\x12\x0c\xc96x\x0e7 \xec\xe9\xdd\xb9\ -\x89\xc4~6\x08j\x01\x9cs\x10\x00\x8d\x15\x1e\xb4y\ -\xc3P\x1bu\xb0\xf4\x8d\x87\xc6r\xaa\xcf3\x1b\x08\xac\ -f\x8a\xe3{\x1bP}\xa0\x99\xca\x11\x05\xf7\xdcp\x1c\ -\xbb\x8e\x98\xb1\xb7\xe2b\x1cW\x05%\xe1\xd8\x9e:\xa1\ -\xee\xb0\x0b}\xafLTR\x12LB\xb3\x87\xa1-\xc8\ -\xa16\xe8\x10;\x22\x0dm\xf5\x1ex*\x1a\xbfS~\ -\xe1'\xc8\xc1u\xdc\x87\x90/\x0cY\xe1\xed\x873\x12\ -\xac \x94\x9c\xd0\xd1\x81h\x8b\x80\xa0\xbbM\xda\xb3\xbd\ -\x96\xf8\x9a\xdb\xc4\x1b\x85ChS\xa9\xd17>\x88\xd2\ -/b/\xeeqU\xc8\x1f\xc1\xbe\x9d\x15\x82%\xde\xc8\ -/\xbb&\x89\x98\xa2Tp\xb9\xdb\xe5^}\xbc\x05Z\ -\xbb\x11m\x0d^x\x8e\xd6w+\xef\x06\xbda\x04\x03\ -\x11\x04\xdc!\x18R\xec\x88I\x89\xee\xd0\xd4E\xa1}\ -\x5c%0\x19\xe5\x1fT\xa1\xa5\xd6\xa7\x8a\xe9\xe3\x83u\ -h\x04\xd7\x1d\xaa\xc4\x9f\xa5k\xf0\xfb\xd7\x0d?\x9eq\ -\x95\xa7\xdeG>\xd9\xfc-\x92\x07\xc5 mh\x0c\x02\ -\x92\x00\x97\x97\x81\xaa\x04\x18\x92m\xd0F\x1b\xe1=\xd6\ -\x80\xb6\x86\xae\xf2q\xd3q/\xb46\x03\xe2\xae\xea\x0b\ -\xd5\x89tO\x08`6PX\x8d@\xf5\xfe&T\xed\ -k\x84,\xb5\xd7W#n\xa8\xc2W\xef\xa5\xe0\xb1\xf0\ -\x8d?\xceq\x15\x00T\x1fhBs\x95\x07)\x83b\ -\xd0g@4\xbc\xbe\xf6~O\xd4\xaa`\x1f\x94\xdc\xae\ -W\x1d\xac\xeb8\xc4!\xea\xd5\xb0\xf6K\xe8t\xa4\xc7\ -\xa8oW-=5^|\xb2\xa3\x06\x913t\xf4\x1d\ -k\x07\xe3,\xda\x22\x08\xa8r\xee)\xeb\x1c\xed\xea)\ -{\x06\x08\x14\xdb\x09\x90\xf6}\xfa\xbdacR\xa16\ -\xe9P\xdb\xd8\xb9}\xe1\x0a\x03g\xbc\xd3\xd8J%\x02\ -16\x01,\x10\xc4\xa1\xcfk\xe1i\xec\xfd!:\x0e\ -\xd4sQ\xf8\xe5'E\x97\x1f\xb8\xa0\x11uE\xe6p\ -\x11L6\xc7\x86\x9b\xb0\xe7\xcb\x16\xb8<\xdf]/\x85\ -\xfc\x11|\xb6\xf5\x10\xe2\xd2\xcc\xc8\x18\x99\x88\xc8\x89q\ -\xbb\xa4\xb4\x8f\x99\xc8\x09\x8c(m\xaf\x87\xf4*\x86\xa3\ -\xbb\xabQ{\xa8\xf7\xc7\x19\x0dz\x8a\xd1\xd7\xc6\xc2\xad\ -\xb1\xb5IP\x91O\x8a.\xf0\xd6k\x0bJ\xf3\xef\xbf\ -Y\xb4\x8e\xee\x9b\x00\x97'\x1a\xafnl\xc0\xc62W\ -\xaf~\xdbP\xe1EC\x85\x17\x19#\xe2\x918\xc0\x0e\ -\x7f\x84\xb6O\x8fy{6\xb3\x18\x08j\xcb\x9b\xf0\xf5\ -7M\x90\xa5\xde\xed\x1eB\x80\x9f\x8f6c\xd1\x9c\x14\ -\x18\xa3\x04\xbc\xb7\x9fe\xbc\xf4\xbe\x94\x0d`\xce\xbfe\ -\xb80e\xca\x82\xd8\xb3}\xae3\xa8\x1e[\xf5\x8e\xb2\ -\xf1/\x7fW\xc0\x04\x15\xe6\xcdJ\xc6k\xf9\x031\xa4\ -\x7fT\xaf\x9d8\xba\xa7\x1e\xbbJ\x0e\x22\xd4\xe4\x81U\ --\xc1\x1e\xa5\x00~?v\x95\x94\xe3\xc8\x97\xf5\x1d \ -\x09\x22\x85F\xd7\xfd,`\xf8 =\xfe\xfcD?,\ -^\x90\x06\x85\x08X\xfb\x0f\x86Wv\xb2\xf7\x94\xb0\xea\ -\xac\xc7\x12\xc7M\xcf\xee{\xde8j\xd2\x1d\x0f\x0cT\ -$\xb2\x88pb\xe4\xe0\xc7\xb9,\x15\x94\xbe\xb5\xaa\xe3\ -\xaf\x15\x99\xd3\xe7]\xc3\x81\x1c\xebe\xe3+Y\xdc\xf5\ -YY\xa3\x85\xb4_\x0e\xa60h\x81\xb7\xdfwcM\ -Q=\xea\x1a#\xe7\x9c\x0cD\x95\x80\xb4\x11q]Z\ -\x18\x00\x88\xb6\x8a\xf8\xf5\x8cxd\xde`GH\x02J\ -\xf70\xbc\xb3W)\x8f4~\xb3\xb1\xf1\xcb\xd7b\x14\ -N\x0b\xca\x9c\xcb\xf6\x9f\x5c\xefp\xe4\xdaB\x94\xcfa\ -\x0c\xfd\x08A\x1d\xe1\xf2\x8b%\xce\xe7\x8e\xfd \xa0&\ -O\xce\xb1DT\xf4^B\x90f\x12\xb4\x8b\xd7\xaf\x7f\ -\xb2a\xe2\xed\x0f\xf6\xe7\xb24\x1f\xa0\x9f\xe99\xdf\x1e\ -$\xec>\x80\xc8V\x8d\xb8t\xdd\xba\xa5\x81)3\x1f\ -\x9c\xa8\xc9\xb8%;~\xe05\x03\xa6]I\xd2~:\ -\x90\xa2-\xc8\xb0nS\x03\xde\xda\xee\x82?\xf0\xfd\x93\ -\x0f\xa1\x04\x89\xfd\xed\x105\x02T\x1a\x01\xa1@\x04\xd5\ -\xfb\xdb\x81\xd2i)\x1c\xe3\xa3q\xf7\xb4xh5\x04\ -\xdf\x1c\xe7X\xff\x11\xf3\x1c\xfe\xe6\x93j\xb9b\xdb\xd2\ -M\xaf?\xf5\xea\xa4I\xf7\x1ae\xad\xe9a\x01\x84B\ -\x91\xd6BP\xdf\xc4\xc1\xae&\x8a\xf0\xa7\x92\x8d\xcb\xbe\ -\xbd\xe9\xce\xfbbUa\xdd\x22\x01\xc4\x0dAY]\xbc\ -~EC\xaf\x80\x1a\xecp\xa8\xd3\x912\x0b`#\x04\ -\x82\xbf\x16o(\xf8\x10\x00~\xe2\xc8\xd5Y\x18\xb9\xac\ -l\xe3\xf2\xdd\x99Y9\x93\x01:\x81P\xe9\xf9\xe2\xc2\ -gw\xdf<\xfd\xfe\x91\x22W\xcd\xa6\x1c\xc7I\xb8u\ -\xa5j\xf8\xaf\xfb\xa9\xd5\xdam\xa3\xaf\x1a\x16?q\x04\ -%}\xe3\x08\xea\x1a#xiC=\xde~\xbf]\xdf\ -\xee\xdd\xb8\xca\x04\x83U\xdb1\xaeR\x14\x0e_s\x00\ -\x9e\x86\x00~>\xda\x8c;\xa7\xc4bP?=\x8e4\ -rl\xfa\x9c){\xf6|\xed\xd5\xb1\xc6\xc9\x87\x8f~\ -\xf2\xb9\xa1\xadu\x16\xe7,\x81I\xaaW\xca6?s\ -d\xc2\xd4\xdc\x0c\x22\xe07\x10\xc8\x8e\xad\x85\xcb\xdf\xbd\ -yZ\xee\x90\xe6\x8a\xc0\xc1\x93Z\xdb\xcd\xb7\xdd?D\ -T\xd4\xb3\x00\xb2w[\xd1\xb2W\xce\xec\x0f;\x015\ -nF\xf6\x15\x22\x13~\xcb\x18\xd9\xa9'\xd5N\xa7\xd3\ -\xa9\x00@\xe6\xad\xf3\xc7q\xca&\x11\x10?\xe7\x5c\xab\ -\x92#y\x9b6\xadr\x8d\x9b\x99m\xa2\xb2\xb0\x10\x5c\ -p\xcb\xb2\xef\x8d\xedo\xad\xae\x9b\xe0\xc8\x99\x03JF\ -1\x905\x833\x97)M\xae\x06\xe7O\x86D\xa7\xce\ -\xbcN\x80Q\x0b\xec\xfa\xc6\x8f\xd5o\xd4c\xdf\xa1\xef\ -N\xf1\xa9Cc!\xa8\x050\x99\xc1\xef\x0e\xa1\xf9\xb8\ -\x17}Su\x98{w\x02\xae\x1cfDD\x02\xde\xda\ -\xc5\xb0\xe3\xcb\x96\xe3\x01n\xb9\xab\xed\x1f\x0b\xea\x18!\ -\xd9\x9c\x93\xdd\xdb\x9c\xf9/\x8d\x9d\x91\x93\xa6b\xe4>\ -\x80\x8a\x11\xce\x9e\xdc\xee\xcco\xc9\xbcm~4g\xca\ -\xc3\x04\x82\x9f16\x94\x10\xf2\xd2\xd6\x0d\xcbKO\xd3\ -\xcf\xef\xa6\x84\x8f!\x84\xac*.\xcc\xdf\xd5\x09\xa8\x89\ -\x8e\xb9}\x00q\x11'Ju B\x9e\xdf\xb9\xb9\xc0\ -\x03\x00\x99\xb7\xe5\x0e\xe1\x0a\xf2@\xf0\x191\x07V\x95\ -\xac^\xdd6ujnFD\x85\xdf\x00\xd0\x81\xa3\x0f\ -\xa7d\xe9\xb6\xc2\xe5\x1fMt\xe4\xfc\x84\x80\xdc\xa3\x00\ -\x9bJ\x9d\xf9o\x8fsd\x0f\x16\x88\xf0\x18\x08\xf9\x22\ -i\xdc3\x95Q\x22\x16\xffb0I\x1f;\x9c\x82\x12\ -\xa0\xec\xbd\x16\xac\xd9P\x8f\xc6f\xa9\xc7q\x95,)\ -\xa8;\xd4\x02\xabI\xc0\xad\xe3\xa2\xe1\x98\x10\x03\xbd\x96\ -\xa2\xec+\x86\xed_\xf3\xa3m\x11\xb6\xb8n\xc7\xc3j\ -p>L&l\xc9\xdb\x85\x05\x15\x93\xa6\xcfs0\xce\ -o\x13\x80\xe7\xb7l\xc8\xdf1\xc9\xf1\xc0@\x85\x0a\x8b\ -\x08\xb8\x97p\xae\x85\x22\x14\x94l\x5c\xf6\xed\xec\xd9\xb3\ -U5n\xfd\xaf\x08\xc1U\x22\xc5\xe2\xcd\x85\xed\xca\xe6\ -\x09\x15\xf7^\x02>\x98\x04\xd9#\xc5\xc5+\x1a\xc8\xc9\ -Y\x9e \x10\xba\xa5p\xf9q\x00\xb8a\xca\xc3v\x9d\ -*2\x0b\xe0\x86\x88D\x9f\xfb\xdb\xa6\xa5\x8d]\xce\x1c\ -L\xbb\x7f\xc8\xf6\x8d\xab\xf6M\xbd}~jD\xe6\x0f\ -r\xa242\xd1Z`\x88\xec\x0b\x04y\x9f\xf9 J\ -\x9c\xa4`\xe9\xf6\xb7\x0a\xea&L\xcf\x19\xa4QG=\ -\xa2\x1f>\xab6.>%w\xc6\xb5T\xbc2\x9d\xc2\ -\xed\x95\xb1\xe5\x1d\x17^,\xac\xefV\xf9\x94%\x05Y\ -\xe3\xa3qoV<\x8cQ\x02\x0e\xd6q\xbc\xf6!\xe3\ -\x87k\xfck\x02\xbb\x96E\x0b\x01\xdf#\x9b6\x15\x1c\ -\x18;#'\x8d*\xc8\x11\x08\x91}\x8d\xbb\x17\x99L\ -W\xeb\x98F\xba\x97\x10\x0cP\xe4\xc8\x92\xd2\xb7VU\ -\x8ew\xe4\x8c(u\x16\xec9\xfb\x88\x8e\xce\xe1`j\ -I#>\xfa\xb7uK\x03'?\xf7\xd3\xb0\xf7\x9fo\ -\xfc\xd9\xdd\x85\xa3\xc6e\xe5\x5c'p\xba\x90P\xf2\xa7\ -\x92\xa2e\xdd\xfe\xd3r\xdc\xb8l\x8dh\x14\xe7\x03$\ ->\xc2y\xdevg~\xcb\xa4\xe993\x19\xe8\x14\x81\ -(\xcfl)\x5c\xf1\xe9\x98\xc99\x16\x83\x9a>\x03\xc2\ -\x9bi\xd0\xfbdq\xf1\xcb\xbe\x19\xf9\xc14\xb5V\x95\ -wy\x1f\xdc}\xe7u\x14q\xe6v\xfeZ\xfab\x0d\ ->\xd9\xdd\xda\xe9\x1e\x83\xfa\xea\x913+\x09C\x07\xe8\ -\xd1\x1a\x04^\xda\xa9\xe0\xcbc\xfc\xcd\x18\x8b\xf8P\xfe\ -LrtT\xe6l}\xbc\xce\x90\x0d\xf0>\x8c\xcby\ -e\xceg\x9bnq\xe4^\xce\x80s\xa6\x82\x93\ -\xe9\x84\xd3g\xb7:\x97\xfd\xb3}\xac5o\x1a\xe7\xfc\ -A\x05\xda\xcc2\xe7SM\xe3\xa7\xde\x9fJ\x05\xf5\xfb\ -\x80r\xc7V\xe7\xca\x7f\x9ey\x8d9/\xf1\x81~I\ -.\xbaa0\x1d>\xf5J\x0a\xa3\x0e\xd8s \x80\xaf\ -\xf7\x07\x00\x02\x0c\xe9\xaf\xc3\xa8\xcb\x8d\x88\xc8@\xc9n\ -\x86w\xf6\xf1c\x01?\xbf\xf7\xb5l\xd5\xce.\xfe\xdc\ ->\x7f\x14\x95\x95\x22\xceC?\xdf\xea|\xa1\xe6F\xc7\ -Cf-\x09\xbfK\x08\x7f\xb1\xa4h\xc5_\x00`\xca\ -\xf4\x05C%\xae\xf6\xcb\xb4\xa6\ -\x17\xef\xba9>p\xe6x\xcd\x10\xeb]\x00\xf0t\x95\ -\xa4[\xb8i\xd3\x93-\x99\x8e\xdc;\x002Y\xa0\xe2\ -\xc2\xcdE\xcf\x1c\xe9\x02jV\xce\xbd\x00\xb9\x99E\xc4\ -\xdf\x95m\xee\xfa\xfd9\x035qz\xce\xf3`\xdcK\ -\xc3\x9a'\x8b\x8b\x97t{$e\xf2\x8cEi2\x0f\ -\x95\x10\xd0\x87K\x8a\x96o\x05\x80\x89\xd3\xe6\xf6\x87 \ -\xfcU\xe0X\xb2\xc5\x99\xdf\xe5\x9f\x9a\xab\xde\xe3\x86\x0f\ -\xf6\xcb\xf3\x93\xcc\xe4\xd11\x83\x89\xa8\x12\x80\xcf\x8e2\ -^\xd9L\xfe\xaa\x88B\xce\xda_\x11\xcf\xd9&D\x9c\ -\x09\x8fPB\x1f:I\x11\x99\xd3\xe7M\x04\xf8\x1f\x05\ -\xc2\x1d'\x89\xf9l6*s\xb6>!*\xeaa\xae\ -\x10\xc2\xfc\xd2\x13ee\xcf\x86\xcf\x1bP'\x07\x9e\xdd\ -n\xc7\x09s\xac\xd4\xa0\xcf\x01W\xe2|\x11,:\x99\ -9o\xbf}\x8e\xb5U\xd1<\x0a\x0e\x9f\xe2\xcbx\xaa\ -\xacln\xb7N-\xde\xc2\xe3\x0e7*\xd7\x1850\ -E\x10\xdc\xfe\xc2]\xc6\xc6\xee\xd6\xdes\xcf=\xda\xe6\ -\xa0\xed1\xce\xb9A\xa4\xe4\x7fO&\xa2)S\xee\x8b\ -\x8d\xa8u\xbf'\x9c\xb8\xfc*q\xcd\xce\xd7\x97Tw\ -\xcf\xb3+5=\xf9s^e\x961c\xc6\x88\xa6\xd8\ -Q3\x15\xb0_\x10EY\xbc\xf5\xc48~\xd4\xa8\xd9\ -\xaa\x84\x8c\xa8\xdf\x82\xf0\xab\x08\x17\xf3J\x9cK\x8f\xfd\ -+t\xae\x89w>\xd4\x07\xe1\xf0\x13`d\xe7\xd67\ -\xf3\xd7\x9e\x94[\xc7\xde\xb1p\xa0\x18\x91\x1e\x22\x9c\xff\ -\xa3\xc4Y\xb0\xf6\x82\xeaQ\xe3fd_!0\xf1\x11\ -\xae\xf0\xd2m\x1b\x0b^\xee\xe8\xffn\xcd\xbd\x89S>\ -\x93\x02\xabOV\xf6\xffj\x9bp\xeb\xdck\x09\x11\x1e\ -\xa6`/\x16;W\x14\x9fJ<\x0f8\x08\xa7\xf7@\ -\x22\xbf\xdb\xbai\xf9W\x17\x06\xa8\x99\x0b\x93=\x11\xc9\ -\xf5\xb13?\x08\x00\xe3\x1c\xd9\x83E*\xce\x03#\xc7\ -J\x9c\xcb\x17\xe3\xdfl\xb3g\xcfV\xd5y\xa3~\xc3\ -\x19\x06\x10H+N6\xba?q\xe4\xeabDf\xea\ -\xae\x8f\xfb\xb7l\xbd\x8e\x1a\xc4\x91;V!d\x8c\x10\ -R\xf2\x8b\x8b\x7f\xb8C\xe7\xc3\xc69\xb2c\x04\xaaZ\ -\xc4\x99\xf2\xfe\xb6\xd3\xa2\xeb\xe2\xb0\xb3\x89\xd5\x17\xd8\xf2\ -\xf2\xf2(.\xd9%\xbbd\x97\xec?\xc0\xfe\x0f\x14\xd5\ -\xea\x92\xe4\xa3Oi\x00\x00\x00\x00IEND\xaeB\ -`\x82\ +l=\x22#191919\x22/>\x0a\x0a\ +\x00\x00\x012\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M12.4501\ + 37.65L10.3501 3\ +5.55L21.9001 24L\ +10.3501 12.45L12\ +.4501 10.35L24.0\ +001 21.9L35.5501\ + 10.35L37.6501 1\ +2.45L26.1001 24L\ +37.6501 35.55L35\ +.5501 37.65L24.0\ +001 26.1L12.4501\ + 37.65Z\x22 fill=\x22#\ +191919\x22/>\x0a\ +\x0a\ \x00\x00\x012\ <\ svg width=\x2248\x22 h\ @@ -781,7 +562,7 @@ path d=\x22M12.4501\ .5501 37.65L24.0\ 001 26.1L12.4501\ 37.65Z\x22 fill=\x22#\ -667085\x22/>\x0a\ +E6E6E6\x22/>\x0a\ \x0a\ \x00\x00\x04\x83\ <\ @@ -856,9 +637,9 @@ C39.7667 22.7784\ .8215 26.1666 38\ .2549 26.4 37.59\ 29 26.4Z\x22 fill=\x22\ -#667085\x22/>\x0a\x0a\x0a\ -\x00\x00\x037\ +\x00\x00\x036\ <\ svg width=\x2224\x22 h\ eight=\x2224\x22 viewB\ @@ -874,7 +655,7 @@ path d=\x22M0 12C0 \ 6274 24 12 24C5.\ 37258 24 0 18.62\ 74 0 12Z\x22 fill=\x22\ -#E6E6E6\x22/>\x0a\x0a\x0a\x0a\x0a\ +l=\x22#E6E6E6\x22/>\x0a\x0a\ +\x00\x00\x01f\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M24.45 4\ +2V39H39V9H24.45V\ +6H39C39.8 6 40.5\ + 6.3 41.1 6.9C41\ +.7 7.5 42 8.2 42\ + 9V39C42 39.8 41\ +.7 40.5 41.1 41.\ +1C40.5 41.7 39.8\ + 42 39 42H24.45Z\ +M20.55 32.75L18.\ +4 30.6L23.5 25.5\ +H6V22.5H23.4L18.\ +3 17.4L20.45 15.\ +25L29.25 24.05L2\ +0.55 32.75Z\x22 fil\ +l=\x22#E6E6E6\x22/>\x0a\x0a\ \x00\x00\x00\xdf\ <\ svg width=\x2248\x22 h\ @@ -926,8 +732,8 @@ path d=\x22M18.9002\ 8.9002 31.4L38.1\ 002 12.2L40.2502\ 14.35L18.9002 3\ -5.7Z\x22 fill=\x22#667\ -085\x22/>\x0a\x0a\ +5.7Z\x22 fill=\x22#191\ +919\x22/>\x0a\x0a\ \x00\x00\x01V\ <\ svg width=\x2248\x22 h\ @@ -950,7 +756,41 @@ path d=\x22M9 42C8.\ L31.05 17.4L33.2\ 15.25L42 24.05L\ 33.3 32.75Z\x22 fil\ -l=\x22#667085\x22/>\x0a\x0a\x0a\ +\x00\x00\x01\xf6\ +<\ +svg width=\x2248\x22 h\ +eight=\x2248\x22 viewB\ +ox=\x220 0 48 48\x22 f\ +ill=\x22none\x22 xmlns\ +=\x22http://www.w3.\ +org/2000/svg\x22>\x0a<\ +path d=\x22M9 39H11\ +.2L33.35 16.85L3\ +1.15 14.65L9 36.\ +8V39ZM39.7 14.7L\ +33.3 8.29998L35.\ +4 6.19998C35.966\ +7 5.63331 36.666\ +7 5.34998 37.5 5\ +.34998C38.3333 5\ +.34998 39.0333 5\ +.63331 39.6 6.19\ +998L41.8 8.39998\ +C42.3667 8.96664\ + 42.65 9.66664 4\ +2.65 10.5C42.65 \ +11.3333 42.3667 \ +12.0333 41.8 12.\ +6L39.7 14.7ZM37.\ +6 16.8L12.4 42H6\ +V35.6L31.2 10.4L\ +37.6 16.8ZM32.25\ + 15.75L31.15 14.\ +65L33.35 16.85L3\ +2.25 15.75Z\x22 fil\ +l=\x22#E6E6E6\x22/>\x0a\x0a\ \x00\x00\x01f\ <\ @@ -975,7 +815,7 @@ H6V22.5H23.4L18.\ 3 17.4L20.45 15.\ 25L29.25 24.05L2\ 0.55 32.75Z\x22 fil\ -l=\x22#667085\x22/>\x0a\x0a\x0a\ " @@ -1004,10 +844,34 @@ qt_resource_name = b"\ \x07\x11\xd4\xa7\ \x00u\ \x00s\x00e\x00r\x00M\x00a\x00s\x00k\x00.\x00s\x00v\x00g\ +\x00\x0f\ +\x0d\x9a\xc0'\ +\x00u\ +\x00p\x00d\x00a\x00t\x00e\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ \x00\x06\ \x07\x87WG\ \x00q\ \x00t\x00.\x00p\x00n\x00g\ +\x00\x0f\ +\x04W\x5c\xe7\ +\x00l\ +\x00o\x00g\x00o\x00u\x00t\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ +\x00\x0b\ +\x08bq\x87\ +\x00o\ +\x00k\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ +\x00\x0d\ +\x01\xb7Pg\ +\x00u\ +\x00s\x00e\x00r\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ +\x00\x0f\ +\x0d\x1a\x9a\x87\ +\x00d\ +\x00e\x00l\x00e\x00t\x00e\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ +\x00\x0d\ +\x06\xf7pG\ +\x00p\ +\x00l\x00u\x00s\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ \x00\x0a\ \x08\xab\xd7\x87\ \x00u\ @@ -1020,14 +884,14 @@ qt_resource_name = b"\ \x0b\x07W\xa7\ \x00e\ \x00d\x00i\x00t\x00.\x00s\x00v\x00g\ -\x00\x0e\ -\x05\x92p\xc7\ -\x00t\ -\x00e\x00s\x00t\x00s\x00e\x00r\x00v\x00e\x00r\x00.\x00p\x00n\x00g\ \x00\x09\ \x06\x98\x8e\xa7\ \x00c\ \x00l\x00o\x00s\x00e\x00.\x00s\x00v\x00g\ +\x00\x0e\ +\x0ea+\x07\ +\x00c\ +\x00l\x00o\x00s\x00e\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ \x00\x08\ \x06\xb6W\xa7\ \x00d\ @@ -1036,6 +900,10 @@ qt_resource_name = b"\ \x09\xc5UG\ \x00u\ \x00s\x00e\x00r\x00.\x00s\x00v\x00g\ +\x00\x0e\ +\x03N\xcdG\ +\x00l\ +\x00o\x00g\x00i\x00n\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ \x00\x06\ \x07^Z\xc7\ \x00o\ @@ -1044,6 +912,10 @@ qt_resource_name = b"\ \x06\xc91\x07\ \x00l\ \x00o\x00g\x00o\x00u\x00t\x00.\x00s\x00v\x00g\ +\x00\x0d\ +\x0f\xef\x10'\ +\x00e\ +\x00d\x00i\x00t\x00_\x00d\x00a\x00r\x00k\x00.\x00s\x00v\x00g\ \x00\x09\ \x0e\x01\xbcg\ \x00l\ @@ -1059,34 +931,50 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x16\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x004\x00\x02\x00\x00\x00\x0d\x00\x00\x00\x05\ +\x00\x00\x004\x00\x02\x00\x00\x00\x15\x00\x00\x00\x05\ \x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x12 \ +\x00\x00\x01\x9d$4\x97\x05\ +\x00\x00\x02\x02\x00\x00\x00\x00\x00\x01\x00\x00(\xa4\ +\x00\x00\x01\x9d$4\x97\x03\ \x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x13\x96\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x00\xf6\x00\x00\x00\x00\x00\x01\x00\x00.-\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00/c\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x01L\x00\x00\x00\x00\x00\x01\x00\x008\x08\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x01\x9d$4\x97\x04\ +\x00\x00\x00\xae\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xe3\ +\x00\x00\x01\x9d$4\x97\x03\ +\x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x1ew\ +\x00\x00\x01\x9d$4\x97\x02\ +\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x00 \xe3\ +\x00\x00\x01\x9d$4\x97\x02\ +\x00\x00\x026\x00\x00\x00\x00\x00\x01\x00\x00*\xf1\ +\x00\x00\x01\x9d$4\x97\x03\ +\x00\x00\x012\x00\x00\x00\x00\x00\x01\x00\x00\x17\x11\ +\x00\x00\x01\x9d$4\x97\x04\ \x00\x00\x00Z\x00\x00\x00\x00\x00\x01\x00\x00\x00\xc4\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x007%\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x01\x5c\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xf3\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x003\xea\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x11\x9b\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xe4\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ -\x00\x00\x01f\x00\x00\x00\x00\x00\x01\x00\x009b\ -\x00\x00\x01\x8d\x87\xa2.\x0a\ +\x00\x00\x01\x9d\x0b\x9dRO\ +\x00\x00\x02$\x00\x00\x00\x00\x00\x01\x00\x00*\x0e\ +\x00\x00\x01\x9d$4\x97\x04\ +\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x04L\ +\x00\x00\x01\x9d\x0b\x9dRN\ +\x00\x00\x00\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x11=\ +\x00\x00\x01\x9d$4\x97\x04\ +\x00\x00\x01R\x00\x00\x00\x00\x00\x01\x00\x00\x17\xd5\ +\x00\x00\x01\x9d$4\x97\x04\ +\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x00%j\ +\x00\x00\x01\x9d$4\x97\x04\ +\x00\x00\x01\x86\x00\x00\x00\x00\x00\x01\x00\x00\x1c}\ +\x00\x00\x01\x9d$4\x97\x03\ +\x00\x00\x01l\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xc6\ +\x00\x00\x01\x9d$4\x97\x02\ +\x00\x00\x01\x0e\x00\x00\x00\x00\x00\x01\x00\x00\x15Z\ +\x00\x00\x01\x9d$4\x97\x02\ +\x00\x00\x00x\x00\x00\x00\x00\x00\x01\x00\x00\x01[\ +\x00\x00\x01\x9d$4\x97\x04\ +\x00\x00\x02p\x00\x00\x00\x00\x00\x01\x00\x00.E\ +\x00\x00\x01\x9d$4\x97\x03\ +\x00\x00\x01\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x1f\xad\ +\x00\x00\x01\x9d$4\x97\x02\ +\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x00,K\ +\x00\x00\x01\x9d$4\x97\x03\ " def qInitResources(): diff --git a/examples/demos/colorpaletteclient/restservice.py b/examples/demos/colorpaletteclient/restservice.py index 076c56e3..852c50fe 100644 --- a/examples/demos/colorpaletteclient/restservice.py +++ b/examples/demos/colorpaletteclient/restservice.py @@ -12,19 +12,6 @@ QML_IMPORT_NAME = "ColorPalette" QML_IMPORT_MAJOR_VERSION = 1 -class ApiKeyRequestFactory(QNetworkRequestFactory): - """Custom request factory that adds the reqres.in API key to all requests""" - - def createRequest(self, path, query=None): - """Override to add API key header to every request""" - if query is None: - request = super().createRequest(path) - else: - request = super().createRequest(path, query) - request.setRawHeader(b"x-api-key", b"reqres-free-v1") - return request - - @QmlElement @ClassInfo(DefaultProperty="resources") class RestService(QPyQmlParserStatus): @@ -37,7 +24,7 @@ class RestService(QPyQmlParserStatus): self.m_qnam = QNetworkAccessManager() self.m_qnam.setAutoDeleteReplies(True) self.m_manager = QRestAccessManager(self.m_qnam) - self.m_serviceApi = ApiKeyRequestFactory() + self.m_serviceApi = QNetworkRequestFactory() @Property(str, notify=urlChanged) def url(self): diff --git a/examples/demos/colorpaletteclient/server/assets/colors.json b/examples/demos/colorpaletteclient/server/assets/colors.json new file mode 100644 index 00000000..76e690c4 --- /dev/null +++ b/examples/demos/colorpaletteclient/server/assets/colors.json @@ -0,0 +1,74 @@ +[ + { + "id": 1, + "name": "cerulean", + "color": "#98B2D1", + "pantone_value": "15-4020" + }, + { + "id": 2, + "name": "fuchsia rose", + "color": "#C74375", + "pantone_value": "17-2031" + }, + { + "id": 3, + "name": "true red", + "color": "#BF1932", + "pantone_value": "19-1664" + }, + { + "id": 4, + "name": "aqua sky", + "color": "#7BC4C4", + "pantone_value": "14-4811" + }, + { + "id": 5, + "name": "tigerlily", + "color": "#E2583E", + "pantone_value": "17-1456" + }, + { + "id": 6, + "name": "blue turquoise", + "color": "#53B0AE", + "pantone_value": "15-5217" + }, + { + "id": 7, + "name": "sand dollar", + "color": "#DECDBE", + "pantone_value": "13-1106" + }, + { + "id": 8, + "name": "chili pepper", + "color": "#9B1B30", + "pantone_value": "19-1557" + }, + { + "id": 9, + "name": "blue iris", + "color": "#5A5B9F", + "pantone_value": "18-3943" + }, + { + "id": 10, + "name": "mimosa", + "color": "#F0C05A", + "pantone_value": "14-0848" + }, + { + "id": 11, + "name": "turquoise", + "color": "#45B5AA", + "pantone_value": "15-5519" + }, + { + "id": 12, + "name": "honeysuckle", + "color": "#D94F70", + "pantone_value": "18-2120" + } +] diff --git a/examples/demos/colorpaletteclient/server/assets/img/1-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/1-image.jpg new file mode 100644 index 00000000..0a74d520 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/1-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/10-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/10-image.jpg new file mode 100644 index 00000000..f6cbd2f0 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/10-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/11-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/11-image.jpg new file mode 100644 index 00000000..f6cd6ace Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/11-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/12-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/12-image.jpg new file mode 100644 index 00000000..60a77881 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/12-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/2-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/2-image.jpg new file mode 100644 index 00000000..b230408e Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/2-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/3-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/3-image.jpg new file mode 100644 index 00000000..7b397878 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/3-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/4-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/4-image.jpg new file mode 100644 index 00000000..fab0bf17 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/4-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/5-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/5-image.jpg new file mode 100644 index 00000000..71eb36ed Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/5-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/6-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/6-image.jpg new file mode 100644 index 00000000..b25b4915 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/6-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/7-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/7-image.jpg new file mode 100644 index 00000000..61ffd455 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/7-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/8-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/8-image.jpg new file mode 100644 index 00000000..ebaeb9be Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/8-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/img/9-image.jpg b/examples/demos/colorpaletteclient/server/assets/img/9-image.jpg new file mode 100644 index 00000000..af37bb75 Binary files /dev/null and b/examples/demos/colorpaletteclient/server/assets/img/9-image.jpg differ diff --git a/examples/demos/colorpaletteclient/server/assets/sessions.json b/examples/demos/colorpaletteclient/server/assets/sessions.json new file mode 100644 index 00000000..292ed829 --- /dev/null +++ b/examples/demos/colorpaletteclient/server/assets/sessions.json @@ -0,0 +1,14 @@ +[ + {"email":"rest.native@qt.io","password":"apassword"}, + {"email":"chief.architect@qt.io","password":"apassword"}, + {"email":"rising.star@qt.io","password":"apassword"}, + {"email":"creative.wizard@qt.io","password":"apassword"}, + {"email":"knuth.developer@qt.io","password":"apassword"}, + {"email":"white.hathacker@qt.io","password":"apassword"}, + {"email":"kind.programmer@qt.io","password":"apassword"}, + {"email":"server.monk@qt.io","password":"apassword"}, + {"email":"fast.fingers@qt.io","password":"apassword"}, + {"email":"runtime.lord@qt.io","password":"apassword"}, + {"email":"stack.flow@qt.io","password":"apassword"}, + {"email":"cute.coder@qt.io","password":"apassword"} +] diff --git a/examples/demos/colorpaletteclient/server/assets/users.json b/examples/demos/colorpaletteclient/server/assets/users.json new file mode 100644 index 00000000..7aa4cfcd --- /dev/null +++ b/examples/demos/colorpaletteclient/server/assets/users.json @@ -0,0 +1,14 @@ +[ + {"id": 1, "email":"rest.native@qt.io","first_name":"Rest","last_name":"Native","avatar":"http://127.0.0.1:49425/img/faces/1-image.jpg"}, + {"id": 2, "email":"chief.architect@qt.io","first_name":"Chief","last_name":"Architect","avatar":"http://127.0.0.1:49425/img/faces/2-image.jpg"}, + {"id": 3, "email":"rising.star@qt.io","first_name":"Rising","last_name":"Star","avatar":"http://127.0.0.1:49425/img/faces/3-image.jpg"}, + {"id": 4, "email":"creative.wizard@qt.io","first_name":"Creative","last_name":"Wizard","avatar":"http://127.0.0.1:49425/img/faces/4-image.jpg"}, + {"id": 5, "email":"knuth.developer@qt.io","first_name":"Knuth","last_name":"Developer","avatar":"http://127.0.0.1:49425/img/faces/5-image.jpg"}, + {"id": 6, "email":"white.hathacker@qt.io","first_name":"White","last_name":"Hathacker","avatar":"http://127.0.0.1:49425/img/faces/6-image.jpg"}, + {"id": 7, "email":"kind.programmer@qt.io","first_name":"Kind","last_name":"Programmer","avatar":"http://127.0.0.1:49425/img/faces/7-image.jpg"}, + {"id": 8, "email":"server.monk@qt.io","first_name":"Server","last_name":"Monk","avatar":"http://127.0.0.1:49425/img/faces/8-image.jpg"}, + {"id": 9, "email":"fast.fingers@qt.io","first_name":"Fast","last_name":"Fingers","avatar":"http://127.0.0.1:49425/img/faces/9-image.jpg"}, + {"id": 10, "email":"runtime.lord@qt.io","first_name":"Runtime","last_name":"Lord","avatar":"http://127.0.0.1:49425/img/faces/10-image.jpg"}, + {"id": 11, "email":"stack.flow@qt.io","first_name":"Stack","last_name":"Flow","avatar":"http://127.0.0.1:49425/img/faces/11-image.jpg"}, + {"id": 12, "email":"cute.coder@qt.io","first_name":"Cute","last_name":"Coder","avatar":"http://127.0.0.1:49425/img/faces/12-image.jpg"} +] diff --git a/examples/demos/colorpaletteclient/server/main.py b/examples/demos/colorpaletteclient/server/main.py new file mode 100644 index 00000000..c66e6e0f --- /dev/null +++ b/examples/demos/colorpaletteclient/server/main.py @@ -0,0 +1,146 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import json +from pathlib import Path +from fastapi import FastAPI, HTTPException, Request, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from pydantic import BaseModel +from typing import Optional + +DATA_DIR = Path(__file__).parent / "assets" + +COLORS_PATH = DATA_DIR / "colors.json" +SESSIONS_PATH = DATA_DIR / "sessions.json" +USERS_PATH = DATA_DIR / "users.json" + +with open(COLORS_PATH) as f: + colors = json.load(f) +with open(SESSIONS_PATH) as f: + sessions = json.load(f) +with open(USERS_PATH) as f: + users = json.load(f) + +# In-memory token store (token: email) +tokens = {} + + +def get_user_by_email(email): + for u in users: + if u["email"] == email: + return u + return None + + +def get_color_by_id(cid): + for c in colors: + if c.get("id") == cid: + return c + return None + + +def require_auth(request: Request): + token = request.headers.get("token") + if not token or token not in tokens: + raise HTTPException(status_code=401, detail="Unauthorized") + return tokens[token] + + +app = FastAPI() + +app.mount("/img/faces", StaticFiles(directory=DATA_DIR / "img"), name="faces") + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +class LoginRequest(BaseModel): + email: str + password: str + + +class Color(BaseModel): + name: str + color: str + pantone_value: str + id: Optional[int] = None + + +@app.post("/api/logout") +def logout(request: Request, user=Depends(require_auth)): + token = request.headers.get("token") + if token in tokens: + del tokens[token] + return {"ok": True} + + +@app.post("/api/login") +def login(data: LoginRequest): + for s in sessions: + if s["email"] == data.email and s["password"] == data.password: + token = f"token-{data.email}" + tokens[token] = data.email + user = get_user_by_email(data.email) + return {"token": token, "id": user.get("id", 1) if user else 1} + raise HTTPException(status_code=401, detail="Invalid credentials") + + +@app.get("/api/unknown") +def list_colors(): + return {"data": colors, "total_pages": 1, "page": 1} + + +@app.get("/api/colors/{cid}") +def get_color(cid: int): + c = get_color_by_id(cid) + if not c: + raise HTTPException(status_code=404, detail="Not found") + return c + + +@app.post("/api/colors") +def add_color(color: Color, user=Depends(require_auth)): + color.id = ( + max((c.get("id", 0) for c in colors), default=0) + 1 + if color.id is None + else color.id + ) + colors.append(color.dict()) + return color + + +@app.put("/api/colors/{cid}") +def update_color(cid: int, color: Color, user=Depends(require_auth)): + c = get_color_by_id(cid) + if not c: + raise HTTPException(status_code=404, detail="Not found") + c.update(color.dict(exclude_unset=True)) + return c + + +@app.delete("/api/colors/{cid}") +def delete_color(cid: int, user=Depends(require_auth)): + c = get_color_by_id(cid) + if not c: + raise HTTPException(status_code=404, detail="Not found") + colors.remove(c) + return {"ok": True} + + +@app.get("/api/users") +def list_users(): + return {"data": users, "total_pages": 1, "page": 1} + + +@app.get("/api/users/{email}") +def get_user(email: str): + u = get_user_by_email(email) + if not u: + raise HTTPException(status_code=404, detail="Not found") + return u diff --git a/examples/demos/colorpaletteclient/server/requirements.txt b/examples/demos/colorpaletteclient/server/requirements.txt new file mode 100644 index 00000000..1363737a --- /dev/null +++ b/examples/demos/colorpaletteclient/server/requirements.txt @@ -0,0 +1,3 @@ +fastapi +uvicorn +python-multipart diff --git a/examples/demos/colorpaletteclient/server/start_server.bat b/examples/demos/colorpaletteclient/server/start_server.bat new file mode 100644 index 00000000..170a6715 --- /dev/null +++ b/examples/demos/colorpaletteclient/server/start_server.bat @@ -0,0 +1,3 @@ +@echo off +cd /d "%~dp0" +uvicorn main:app --host 127.0.0.1 --port 49425 diff --git a/examples/demos/colorpaletteclient/server/start_server.sh b/examples/demos/colorpaletteclient/server/start_server.sh new file mode 100755 index 00000000..2690bc52 --- /dev/null +++ b/examples/demos/colorpaletteclient/server/start_server.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd "$(dirname "$0")" +uvicorn main:app --host 127.0.0.1 --port 49425 diff --git a/examples/demos/documentviewer/abstractviewer.py b/examples/demos/documentviewer/abstractviewer.py index 2c07c10c..2893e8d2 100644 --- a/examples/demos/documentviewer/abstractviewer.py +++ b/examples/demos/documentviewer/abstractviewer.py @@ -4,8 +4,8 @@ from __future__ import annotations from PySide6.QtCore import QObject -from PySide6.QtWidgets import (QDialog, QMenu) -from PySide6.QtCore import Signal, Slot +from PySide6.QtWidgets import QDialog, QMenu, QToolBar +from PySide6.QtCore import QEvent, Signal, Slot from PySide6.QtPrintSupport import QPrinter, QPrintDialog @@ -23,31 +23,38 @@ class AbstractViewer(QObject): super().__init__() self._file = None self._widget = None + self._uiAssets_mainWindow = None self._menus = [] self._toolBars = [] self._printingEnabled = False self._actions = [] self._fileMenu = None - def __del__(self): - self.cleanup() - def viewerName(self): return "" + def eventFilter(self, watched, event): + if event.type() == QEvent.Type.LanguageChange: + self.retranslate() + return False + + def retranslate(self): + pass + def saveState(self): return False def restoreState(self, state): return False - def supportedMimeTypes(): + def supportedMimeTypes(self): return [] def init(self, file, widget, mainWindow): self._file = file self._widget = widget self._uiAssets_mainWindow = mainWindow + mainWindow.installEventFilter(self) def isEmpty(self): return not self.hasContent() @@ -107,16 +114,16 @@ class AbstractViewer(QObject): msg += ": " + message self.showMessage.emit(msg, timeout) - def addToolBar(self, title): - bar = self.mainWindow().addToolBar(title) - name = title.replace(' ', '') - bar.setObjectName(name) + def addToolBar(self): + bar = QToolBar() + bar.setObjectName(self.viewerName() + "ToolBar") + self.mainWindow().addToolBar(bar) self._toolBars.append(bar) return bar - def addMenu(self, title): - menu = QMenu(title, self.menuBar()) - menu.setObjectName(title) + def addMenu(self): + menu = QMenu(self.menuBar()) + menu.setObjectName(self.viewerName() + "Menu") self.menuBar().insertMenu(self._uiAssets_help, menu) self._menus.append(menu) return menu @@ -126,8 +133,13 @@ class AbstractViewer(QObject): # and therefore parented on MainWindow if self._file: self._file = None - self._menus.clear() - self._toolBars.clear() + while self._menus: + del self._menus[0] + while self._toolBars: + self.mainWindow().removeToolBar(self._toolBars[0]) + del self._toolBars[0] + if self._uiAssets_mainWindow: + self._uiAssets_mainWindow.removeEventFilter(self) def fileMenu(self): if self._fileMenu: @@ -138,34 +150,34 @@ class AbstractViewer(QObject): if menu.objectName() == MENU_NAME: self._fileMenu = menu return self._fileMenu - self._fileMenu = self.addMenu("File") + self._fileMenu = self.addMenu(self.tr("&File")) self._fileMenu.setObjectName(MENU_NAME) return self._fileMenu @Slot() def print_(self): - type = "Printing" + type = self.tr("Printing") if not self.hasContent(): - self.statusMessage("No content to print.", type) + self.statusMessage(self.tr("No content to print."), type) return printer = QPrinter(QPrinter.PrinterMode.HighResolution) dlg = QPrintDialog(printer, self.mainWindow()) - dlg.setWindowTitle("Print Document") + dlg.setWindowTitle(self.tr("Print Document")) if dlg.exec() == QDialog.DialogCode.Accepted: self.printDocument(printer) else: - self.statusMessage("Printing canceled!", type) + self.statusMessage(self.tr("Printing canceled!"), type) return - state = printer.printerState() message = self.viewerName() + " :" - if state == QPrinter.PrinterState.Aborted: - message += "Printing aborted." - elif state == QPrinter.PrinterState.Active: - message += "Printing active." - elif state == QPrinter.PrinterState.Idle: - message += "Printing completed." - elif state == QPrinter.PrinterState.Error: - message += "Printing error." + match printer.printerState(): + case QPrinter.PrinterState.Aborted: + message += self.tr("Printing aborted.") + case QPrinter.PrinterState.Active: + message += self.tr("Printing active.") + case QPrinter.PrinterState.Idle: + message += self.tr("Printing completed.") + case QPrinter.PrinterState.Error: + message += self.tr("Printing error.") self.statusMessage(message, type) def maybeSetPrintingEnabled(self, enabled): diff --git a/examples/demos/documentviewer/documentviewer.pyproject b/examples/demos/documentviewer/documentviewer.pyproject index fe1a4dbf..98724e6b 100644 --- a/examples/demos/documentviewer/documentviewer.pyproject +++ b/examples/demos/documentviewer/documentviewer.pyproject @@ -3,6 +3,7 @@ "documentviewer.qrc", "main.py", "mainwindow.py", + "translator.py", "mainwindow.ui", "imageviewer/imageviewer.py", "jsonviewer/jsonviewer.py", @@ -11,5 +12,7 @@ "txtviewer/txtviewer.py", "recentfilemenu.py", "recentfiles.py", - "viewerfactory.py"] + "viewerfactory.py", + "documentviewer_en.ts", + "documentviewer_de.ts"] } diff --git a/examples/demos/documentviewer/documentviewer.qrc b/examples/demos/documentviewer/documentviewer.qrc index 0aeeac22..31264f0e 100644 --- a/examples/demos/documentviewer/documentviewer.qrc +++ b/examples/demos/documentviewer/documentviewer.qrc @@ -1,42 +1,19 @@ - - - images/copy@2x.png - images/copy.png - images/cut@2x.png - images/cut.png - images/go-next-view@2x.png - images/go-next-view-page@2x.png - images/go-next-view-page.png - images/go-next-view.png - images/go-previous-view@2x.png - images/go-previous-view-page@2x.png - images/go-previous-view-page.png - images/go-previous-view.png - images/magnifier@2x.png - images/magnifier.png - images/open@2x.png - images/open.png - images/paste@2x.png - images/paste.png - images/print2x.png - images/print.png - images/qt-logo@2x.png - images/qt-logo.png - images/zoom-fit-best@2x.png - images/zoom-fit-best.png - images/zoom-fit-width@2x.png - images/zoom-fit-width.png - images/zoom-in@2x.png - images/zoom-in.png - images/zoom-original@2x.png - images/zoom-original.png - images/zoom-out@2x.png - images/zoom-out.png - images/zoom-previous@2x.png - images/zoom-previous.png - images/document-open-recent.svgz - images/go-next.svgz - images/go-previous.svgz - images/help-about.svgz - + + + images/copy@2x.png + images/copy.png + images/cut@2x.png + images/cut.png + images/paste@2x.png + images/paste.png + images/qt-logo@2x.png + images/qt-logo.png + images/zoom-in@2x.png + images/zoom-in.png + images/zoom-out@2x.png + images/zoom-out.png + + + documentviewer_de.qm + diff --git a/examples/demos/documentviewer/documentviewer_de.ts b/examples/demos/documentviewer/documentviewer_de.ts new file mode 100644 index 00000000..34014a51 --- /dev/null +++ b/examples/demos/documentviewer/documentviewer_de.ts @@ -0,0 +1,448 @@ + + + + + AbstractViewer + + + &File + &Datei + + + + Printing + Drucken + + + + No content to print. + Kein Inhalt zum Drucken vorhanden. + + + + Print Document + Dokument drucken + + + + Printing canceled! + Druckvorgang abgebrochen! + + + + Printing aborted. + Druckvorgang abgebrochen. + + + + Printing active. + Druckvorgang läuft. + + + + Printing completed. + Druckvorgang abgeschlossen. + + + + Printing error. + Druckfehler. + + + + ImageViewer + + + unknown + unbekannt + + + + Images + Bilder + + + + Zoom &In + &Vergrößern + + + + Zoom &Out + &Verkleinern + + + + Reset Zoom + Zoom zurücksetzen + + + + Cannot read file {}: +{} + Die Datei {} kann nicht gelesen werden: +{} + + + + JsonViewer + + + Json + Json + + + + Json Actions + Json-Aktionen + + + + &+Expand all + &+Alle erweitern + + + + &-Collapse all + &-Alle reduzieren + + + + Bookmarks + Lesezeichen + + + + Json document {} opened + Json-Dokument {} wurde geöffnet + + + + Unable to parse Json document from {}: {} + Json-Dokument aus {} konnte nicht geparst werden: {} + + + + Add bookmark + Lesezeichen hinzufügen + + + + Delete bookmark + Lesezeichen löschen + + + + MainWindow + + + Open Document + Dokument öffnen + + + + File {} could not be opened + Datei {} konnte nicht geöffnet werden + + + + File {} can't be opened. + Datei {} kann nicht geöffnet werden. + + + + About Document Viewer Demo + Über Dokumentenanzeige-Demo + + + + Document Viewer Demo + Dokumentenanzeige-Demo + + + + Pages + Seiten + + + + Bookmarks + Lesezeichen + + + + &File + &Datei + + + + Help + Hilfe + + + + ToolBar + toolBar + Werkzeugleiste + + + + &Open + &Öffnen + + + + Ctrl+O + Strg+O + + + + About Document Viewer + Über Dokumentenanzeige + + + + Show information about the Document Viewer deomo. + Informationen über die Dokumentenanzeige-Demo anzeigen. + + + + Ctrl+H + Strg+H + + + + Forward + actionForward + Vorwärts + + + + One step forward + Einen Schritt vorwärts + + + + Right + Rechts + + + + Back + Zurück + + + + One step back + Einen Schritt zurück + + + + Left + Links + + + + &Print + &Drucken + + + + Print current file + Aktuelle Datei drucken + + + + Ctrl+P + Strg+P + + + + About Qt + Über Qt + + + + Show Qt license information + Qt-Lizenzinformationen anzeigen + + + + Ctrl+I + Strg+I + + + + &Recently opened... + &Zuletzt geöffnet... + + + + Meta+R + Meta+R + + + + + E&xit + &Beenden + + + + Exits the application + Beendet die Anwendung + + + + Ctrl+Q + Strg+Q + + + + %n recent files + letzte Datei + + + + PdfViewer + + + PDF + PDF + + + + Zoom in + Vergrößern + + + + Pages + Seiten + + + + Bookmarks + Lesezeichen + + + + Opened PDF file {} + Die PDF-Datei {} wurde geöffnet + + + + Increase zoom level + Vergrößern + + + + Zoom out + Verkleinern + + + + Decrease zoom level + Verkleinern + + + + PDF Viewer + PDF-Betrachter + + + + RecentFileMenu + + + <no recent files> + <keine zuletzt geöffneten Dateien> + + + + TxtViewer + + + + Edit + Bearbeiten + + + + C&ut + &Ausschneiden + + + + Cut the current selection's contents to the clipboard + Ausgewählten Inhalt in die Zwischenablage ausschneiden + + + + &Copy + &Kopieren + + + + Copy the current selection's contents to the clipboard + Ausgewählten Inhalt in die Zwischenablage kopieren + + + + &Paste + &Einfügen + + + + Paste the clipboard's contents into the current selection + Inhalt der Zwischenablage in die aktuelle Auswahl einfügen + + + + Cannot read file {}: +{}. + Die Datei {} kann nicht gelesen werden: +{} + + + + File {} loaded. + Datei {} geladen. + + + + Cannot open file {} for writing: +{}. + Datei {} kann nicht zum Schreiben geöffnet werden: +{}. + + + + File {} saved + Datei {} gespeichert + + + + ViewerFactory + + + Mime type {} not supported. Falling back to {}. + MIME-Typ {} wird nicht unterstützt. Es wird auf {} zurückgegriffen. + + + + ZoomSelector + + + + Fit Width + An Breite anpassen + + + + + Fit Page + An Seite anpassen + + + diff --git a/examples/demos/documentviewer/documentviewer_en.ts b/examples/demos/documentviewer/documentviewer_en.ts new file mode 100644 index 00000000..c97ea73e --- /dev/null +++ b/examples/demos/documentviewer/documentviewer_en.ts @@ -0,0 +1,3 @@ + + + diff --git a/examples/demos/documentviewer/images/document-open-recent.svgz b/examples/demos/documentviewer/images/document-open-recent.svgz deleted file mode 100644 index 3b6eaf97..00000000 Binary files a/examples/demos/documentviewer/images/document-open-recent.svgz and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-next-view-page.png b/examples/demos/documentviewer/images/go-next-view-page.png deleted file mode 100644 index bd2a3383..00000000 Binary files a/examples/demos/documentviewer/images/go-next-view-page.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-next-view-page@2x.png b/examples/demos/documentviewer/images/go-next-view-page@2x.png deleted file mode 100644 index 5ddcbbcc..00000000 Binary files a/examples/demos/documentviewer/images/go-next-view-page@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-next-view.png b/examples/demos/documentviewer/images/go-next-view.png deleted file mode 100644 index 98b79dea..00000000 Binary files a/examples/demos/documentviewer/images/go-next-view.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-next-view@2x.png b/examples/demos/documentviewer/images/go-next-view@2x.png deleted file mode 100644 index 91940643..00000000 Binary files a/examples/demos/documentviewer/images/go-next-view@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-next.svgz b/examples/demos/documentviewer/images/go-next.svgz deleted file mode 100644 index c6dc7d0f..00000000 Binary files a/examples/demos/documentviewer/images/go-next.svgz and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-previous-view-page.png b/examples/demos/documentviewer/images/go-previous-view-page.png deleted file mode 100644 index ecd3768e..00000000 Binary files a/examples/demos/documentviewer/images/go-previous-view-page.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-previous-view-page@2x.png b/examples/demos/documentviewer/images/go-previous-view-page@2x.png deleted file mode 100644 index f0d91c9f..00000000 Binary files a/examples/demos/documentviewer/images/go-previous-view-page@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-previous-view.png b/examples/demos/documentviewer/images/go-previous-view.png deleted file mode 100644 index 086bd9a1..00000000 Binary files a/examples/demos/documentviewer/images/go-previous-view.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-previous-view@2x.png b/examples/demos/documentviewer/images/go-previous-view@2x.png deleted file mode 100644 index 900860ce..00000000 Binary files a/examples/demos/documentviewer/images/go-previous-view@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/go-previous.svgz b/examples/demos/documentviewer/images/go-previous.svgz deleted file mode 100644 index 971b1d98..00000000 Binary files a/examples/demos/documentviewer/images/go-previous.svgz and /dev/null differ diff --git a/examples/demos/documentviewer/images/help-about.svgz b/examples/demos/documentviewer/images/help-about.svgz deleted file mode 100644 index 001aa7f2..00000000 Binary files a/examples/demos/documentviewer/images/help-about.svgz and /dev/null differ diff --git a/examples/demos/documentviewer/images/magnifier.png b/examples/demos/documentviewer/images/magnifier.png deleted file mode 100644 index 6eb457d9..00000000 Binary files a/examples/demos/documentviewer/images/magnifier.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/magnifier@2x.png b/examples/demos/documentviewer/images/magnifier@2x.png deleted file mode 100644 index ed84af18..00000000 Binary files a/examples/demos/documentviewer/images/magnifier@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/open.png b/examples/demos/documentviewer/images/open.png deleted file mode 100644 index 45fa2883..00000000 Binary files a/examples/demos/documentviewer/images/open.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/open@2x.png b/examples/demos/documentviewer/images/open@2x.png deleted file mode 100644 index 12c2c3c1..00000000 Binary files a/examples/demos/documentviewer/images/open@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/print.png b/examples/demos/documentviewer/images/print.png deleted file mode 100644 index 4ace2614..00000000 Binary files a/examples/demos/documentviewer/images/print.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/print2x.png b/examples/demos/documentviewer/images/print2x.png deleted file mode 100644 index 1c3655be..00000000 Binary files a/examples/demos/documentviewer/images/print2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-fit-best.png b/examples/demos/documentviewer/images/zoom-fit-best.png deleted file mode 100644 index 6a13de4c..00000000 Binary files a/examples/demos/documentviewer/images/zoom-fit-best.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-fit-best@2x.png b/examples/demos/documentviewer/images/zoom-fit-best@2x.png deleted file mode 100644 index 904b41c8..00000000 Binary files a/examples/demos/documentviewer/images/zoom-fit-best@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-fit-width.png b/examples/demos/documentviewer/images/zoom-fit-width.png deleted file mode 100644 index d51fbac6..00000000 Binary files a/examples/demos/documentviewer/images/zoom-fit-width.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-fit-width@2x.png b/examples/demos/documentviewer/images/zoom-fit-width@2x.png deleted file mode 100644 index 4d1fd0b4..00000000 Binary files a/examples/demos/documentviewer/images/zoom-fit-width@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-original.png b/examples/demos/documentviewer/images/zoom-original.png deleted file mode 100644 index 8aa9bb49..00000000 Binary files a/examples/demos/documentviewer/images/zoom-original.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-original@2x.png b/examples/demos/documentviewer/images/zoom-original@2x.png deleted file mode 100644 index d5473007..00000000 Binary files a/examples/demos/documentviewer/images/zoom-original@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-previous.png b/examples/demos/documentviewer/images/zoom-previous.png deleted file mode 100644 index 0ff5c041..00000000 Binary files a/examples/demos/documentviewer/images/zoom-previous.png and /dev/null differ diff --git a/examples/demos/documentviewer/images/zoom-previous@2x.png b/examples/demos/documentviewer/images/zoom-previous@2x.png deleted file mode 100644 index e9909abc..00000000 Binary files a/examples/demos/documentviewer/images/zoom-previous@2x.png and /dev/null differ diff --git a/examples/demos/documentviewer/imageviewer/imageviewer.py b/examples/demos/documentviewer/imageviewer/imageviewer.py index 6843c5d6..b4977a3a 100644 --- a/examples/demos/documentviewer/imageviewer/imageviewer.py +++ b/examples/demos/documentviewer/imageviewer/imageviewer.py @@ -25,7 +25,8 @@ def imageFormats(): def msgOpen(name, image): - description = image.colorSpace().description() if image.colorSpace().isValid() else "unknown" + description = (image.colorSpace().description() if image.colorSpace().isValid() + else ImageViewer.tr("unknown")) return 'Opened "{0}", {1}x{2}, Depth: {3} ({4})'.format( QDir.toNativeSeparators(name), image.width(), @@ -44,38 +45,56 @@ class ImageViewer(AbstractViewer): self.uiInitialized.connect(self.setupImageUi) QImageReader.setAllocationLimit(1024) # MB - def init(self, file, parent, mainWindow): - self.image_label = QLabel(parent) - self.image_label.setFrameShape(QLabel.Box) - self.image_label.setAlignment(Qt.AlignCenter) - self.image_label.setScaledContents(True) - - # AbstractViewer.init(file, self.image_label, mainWindow) - super().init(file, self.image_label, mainWindow) - - self.tool_bar = self.addToolBar(self.tr("Images")) - icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn, QIcon(":/demos/documentviewer/images/zoom-in.png")) - self.zoom_in_act = QAction(icon, "Zoom &In", self) + self.zoom_in_act = QAction(self) + self.zoom_in_act.setIcon(icon) self.zoom_in_act.setShortcut(QKeySequence.StandardKey.ZoomIn) self.zoom_in_act.triggered.connect(self.zoomIn) - self.tool_bar.addAction(self.zoom_in_act) icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut, QIcon(":/demos/documentviewer/images/zoom-out.png")) - self.zoom_out_act = QAction(icon, "Zoom &Out", self) + self.zoom_out_act = QAction(self) + self.zoom_out_act.setIcon(icon) self.zoom_out_act.setShortcut(QKeySequence.StandardKey.ZoomOut) self.zoom_out_act.triggered.connect(self.zoomOut) - self.tool_bar.addAction(self.zoom_out_act) icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomFitBest, QIcon(":/demos/documentviewer/images/zoom-fit-best.png")) - self.reset_zoom_act = QAction(icon, "Reset Zoom", self) + self.reset_zoom_act = QAction(self) + self.reset_zoom_act.setIcon(icon) self.reset_zoom_act.setShortcut(QKeySequence (Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_0)) self.reset_zoom_act.triggered.connect(self.resetZoom) - self.tool_bar.addAction(self.reset_zoom_act) + + self.image_label = None + + def retranslate(self): + if not self._toolBars: + return + self._toolBars[0].setWindowTitle(self.tr("Images")) + self.zoom_in_act.setText(self.tr("Zoom &In")) + self.zoom_out_act.setText(self.tr("Zoom &Out")) + self.reset_zoom_act.setText(self.tr("Reset Zoom")) + + def init(self, file, parent, mainWindow): + self.image_label = QLabel(parent) + self.image_label.setFrameShape(QLabel.Box) + self.image_label.setAlignment(Qt.AlignCenter) + self.image_label.setScaledContents(True) + + # AbstractViewer.init(file, self.image_label, mainWindow) + super().init(file, self.image_label, mainWindow) + + tool_bar = self.addToolBar() + tool_bar.addAction(self.zoom_in_act) + tool_bar.addAction(self.zoom_out_act) + tool_bar.addAction(self.reset_zoom_act) + + def cleanup(self): + del self.image_label + self.image_label = None + super().cleanup() def supportedMimeTypes(self): return self.formats @@ -97,7 +116,8 @@ class ImageViewer(AbstractViewer): orig_image = reader.read() if orig_image.isNull(): - self.statusMessage(f"Cannot read file {name}:\n{reader.errorString()}", "open") + msg = self.tr("Cannot read file {}:\n{}").format(name, reader.errorString()) + self.statusMessage(msg, "open") self.disablePrinting() QGuiApplication.restoreOverrideCursor() return @@ -163,11 +183,11 @@ class ImageViewer(AbstractViewer): if not self.hasContent(): return - painter = QPainter(printer) - pixmap = self.image_label.pixmap() - rect = painter.viewport() - size = pixmap.size() - size.scale(rect.size(), Qt.KeepAspectRatio) - painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) - painter.setWindow(pixmap.rect()) - painter.drawPixmap(0, 0, pixmap) + with QPainter(printer) as painter: + pixmap = self.image_label.pixmap() + rect = painter.viewport() + size = pixmap.size() + size.scale(rect.size(), Qt.KeepAspectRatio) + painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) + painter.setWindow(pixmap.rect()) + painter.drawPixmap(0, 0, pixmap) diff --git a/examples/demos/documentviewer/jsonviewer/jsonviewer.py b/examples/demos/documentviewer/jsonviewer/jsonviewer.py index 0cdf5369..f94ba9f7 100644 --- a/examples/demos/documentviewer/jsonviewer/jsonviewer.py +++ b/examples/demos/documentviewer/jsonviewer/jsonviewer.py @@ -4,13 +4,9 @@ from __future__ import annotations import json -from PySide6.QtWidgets import (QLabel, QLineEdit, QListWidget, - QListWidgetItem, QMenu, QTreeView) -from PySide6.QtGui import (QAction, QIcon, QKeySequence, - QPixmap, QTextDocument) -from PySide6.QtCore import (QAbstractItemModel, QDir, - QIODevice, QModelIndex, - QPoint, QSize, Qt, Slot) +from PySide6.QtWidgets import QListWidget, QListWidgetItem, QMenu, QTreeView +from PySide6.QtGui import QAction, QIcon, QTextDocument +from PySide6.QtCore import QAbstractItemModel, QDir, QIODevice, QModelIndex, QPoint, Qt, Slot from abstractviewer import AbstractViewer @@ -105,14 +101,16 @@ class JsonItemModel(QAbstractItemModel): return None item = self.itemFromIndex(index) - if role == Qt.ItemDataRole.DisplayRole: - if index.column() == 0: - return item.key() - if index.column() == 1: - return item.value() - elif role == Qt.ItemDataRole.EditRole: - if index.column() == 1: - return item.value() + match role: + case Qt.ItemDataRole.DisplayRole: + match index.column(): + case 0: + return item.key() + case 1: + return item.value() + case Qt.ItemDataRole.EditRole: + if index.column() == 1: + return item.value() return None def headerData(self, section, orientation, role): @@ -164,14 +162,21 @@ class JsonViewer(AbstractViewer): def __init__(self): super().__init__() - self._tree = QTreeView() + self._tree = None self._toplevel = None self._text = "" - self._searchKey = None self.uiInitialized.connect(self.setupJsonUi) + self._expand_all_act = QAction(self) + self._expand_all_act.setIcon(QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn)) + + self._collapse_all_act = QAction(self) + self._collapse_all_act.setIcon(QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut)) + def init(self, file, parent, mainWindow): self._tree = QTreeView(parent) + self._expand_all_act.triggered.connect(self._tree.expandAll) + self._collapse_all_act.triggered.connect(self._tree.collapseAll) super().init(file, self._tree, mainWindow) def viewerName(self): @@ -180,33 +185,28 @@ class JsonViewer(AbstractViewer): def supportedMimeTypes(self): return ["application/json"] + def retranslate(self): + if not self._toolBars: + return + self._menus[0].setTitle(self.tr("Json")) + self._toolBars[0].setWindowTitle(self.tr("Json Actions")) + self._expand_all_act.setText(self.tr("&+Expand all")) + self._collapse_all_act.setText(self.tr("&-Collapse all")) + tabIndex = self._uiAssets_tabs.indexOf(self._toplevel) + if tabIndex >= 0: + self._uiAssets_tabs.setTabText(tabIndex, self.tr("Bookmarks")) + for i in range(self._toplevel.count()): + self._toplevel.item(i).setToolTip(self.tr("Toplevel Item {}").format(i)) + @Slot() def setupJsonUi(self): # Build Menus and toolbars - menu = self.addMenu("Json") - tb = self.addToolBar("Json Actions") - - zoomInIcon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn) - a = menu.addAction(zoomInIcon, "&+Expand all", self._tree.expandAll) - tb.addAction(a) - a.setPriority(QAction.Priority.LowPriority) - a.setShortcut(QKeySequence.StandardKey.New) - - zoomOutIcon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut) - a = menu.addAction(zoomOutIcon, "&-Collapse all", self._tree.collapseAll) - tb.addAction(a) - a.setPriority(QAction.Priority.LowPriority) - a.setShortcut(QKeySequence.StandardKey.New) - - if not self._searchKey: - self._searchKey = QLineEdit(tb) - - label = QLabel(tb) - magnifier = QPixmap(":/icons/images/magnifier.png").scaled(QSize(28, 28)) - label.setPixmap(magnifier) - tb.addWidget(label) - tb.addWidget(self._searchKey) - self._searchKey.textEdited.connect(self._tree.keyboardSearch) + menu = self.addMenu() + tb = self.addToolBar() + menu.addAction(self._expand_all_act) + tb.addAction(self._expand_all_act) + menu.addAction(self._collapse_all_act) + tb.addAction(self._collapse_all_act) if not self.openJsonFile(): return @@ -214,13 +214,12 @@ class JsonViewer(AbstractViewer): # Populate bookmarks with toplevel self._uiAssets_tabs.clear() self._toplevel = QListWidget(self._uiAssets_tabs) - self._uiAssets_tabs.addTab(self._toplevel, "Bookmarks") + self._uiAssets_tabs.addTab(self._toplevel, "") for i in range(0, self._tree.model().rowCount()): index = self._tree.model().index(i, 0) self._toplevel.addItem(index.data()) item = self._toplevel.item(i) item.setData(Qt.ItemDataRole.UserRole, index) - item.setToolTip(f"Toplevel Item {i}") self._toplevel.setAcceptDrops(True) self._tree.setDragEnabled(True) @@ -236,6 +235,8 @@ class JsonViewer(AbstractViewer): self._uiAssets_back.triggered.connect(self._back) self._uiAssets_forward.triggered.connect(self._forward) + self.retranslate() + @Slot() def _back(self): index = self._tree.indexAbove(self._tree.currentIndex()) @@ -268,11 +269,11 @@ class JsonViewer(AbstractViewer): message = None try: data = json.loads(self._text) - message = f"Json document {file_name} opened" + message = self.tr("Json document {} opened").format(file_name) model = JsonItemModel(data, self) self._tree.setModel(model) except ValueError as e: - message = f"Unable to parse Json document from {file_name}: {e}" + message = self.tr("Unable to parse Json document from {}: {}").format(file_name, e) self.statusMessage(message, type) self.maybeEnablePrinting() @@ -327,7 +328,7 @@ class JsonViewer(AbstractViewer): return menu = QMenu(self._tree) - action = QAction("Add bookmark") + action = QAction(self.tr("Add bookmark")) action.setData(index) menu.addAction(action) action.triggered.connect(self.onBookmarkAdded) @@ -345,7 +346,7 @@ class JsonViewer(AbstractViewer): return menu = QMenu() - action = QAction("Delete bookmark") + action = QAction(self.tr("Delete bookmark")) action.setData(self._toplevel.row(item)) menu.addAction(action) action.triggered.connect(self.onBookmarkDeleted) diff --git a/examples/demos/documentviewer/mainwindow.py b/examples/demos/documentviewer/mainwindow.py index d35f76e6..8d437ef0 100644 --- a/examples/demos/documentviewer/mainwindow.py +++ b/examples/demos/documentviewer/mainwindow.py @@ -3,12 +3,14 @@ from __future__ import annotations from PySide6.QtWidgets import (QDialog, QFileDialog, QMainWindow, QMessageBox) -from PySide6.QtCore import (QDir, QFile, QFileInfo, QSettings, Slot) +from PySide6.QtCore import (QCoreApplication, QDir, QEvent, QFile, QFileInfo, QLocale, + QSettings, Slot) from ui_mainwindow import Ui_MainWindow from viewerfactory import ViewerFactory from recentfiles import RecentFiles from recentfilemenu import RecentFileMenu +from translator import Translator settingsDir = "WorkingDir" @@ -26,12 +28,24 @@ behavior when hovering over widgets. """ +def getFileUrl(mime): + if mime.hasUrls(): + for url in mime.urls(): + if url.isLocalFile(): + return url + return None + + class MainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_MainWindow() + self._translator = Translator() + self._translator.setBaseName("documentviewer") + self._translator.install() + self._currentDir = QDir() self._viewer = None self._recentFiles = RecentFiles() @@ -40,6 +54,10 @@ class MainWindow(QMainWindow): self.ui.actionOpen.triggered.connect(self.onActionOpenTriggered) self.ui.actionAbout.triggered.connect(self.onActionAboutTriggered) self.ui.actionAboutQt.triggered.connect(self.onActionAboutQtTriggered) + self.ui.actionDeutsch.setData(QLocale.Language.German) + self.ui.actionDeutsch.triggered.connect(self.onActionSwitchLanguage) + self.ui.actionEnglish.setData(QLocale.Language.English) + self.ui.actionEnglish.triggered.connect(self.onActionSwitchLanguage) self._recentFiles = RecentFiles(self.ui.actionRecent) self._recentFiles.countChanged.connect(self._recentFilesCountChanged) @@ -57,14 +75,43 @@ class MainWindow(QMainWindow): @Slot(int) def _recentFilesCountChanged(self, count): - self.ui.actionRecent.setText(f"{count} recent files") + self.ui.actionRecent.setText(self.tr("%n recent files", None, count)) def closeEvent(self, event): self.saveSettings() + def dragEnterEvent(self, event): + if getFileUrl(event.mimeData()): + event.acceptProposedAction() + + def dragMoveEvent(self, event): + event.acceptProposedAction() + + def dropEvent(self, event): + if fileUrl := getFileUrl(event.mimeData()): + if self.openFile(fileUrl.toLocalFile()): + event.acceptProposedAction() + + def changeEvent(self, event): + match event.type(): + case QEvent.Type.LanguageChange: + self.ui.retranslateUi(self) + self.statusBar().clearMessage() + case QEvent.Type.LocaleChange: + self._translator.setLanguage(QLocale().language()) + self._translator.install() + super().changeEvent(event) + + @Slot() + def onActionSwitchLanguage(self): + lang = self.sender().data() + QLocale.setDefault(QLocale(lang)) + event = QEvent(QEvent.Type.LocaleChange) + QCoreApplication.sendEvent(self, event) + @Slot(int) def onActionOpenTriggered(self): - fileDialog = QFileDialog(self, "Open Document", + fileDialog = QFileDialog(self, self.tr("Open Document"), self._currentDir.absolutePath()) while (fileDialog.exec() == QDialog.DialogCode.Accepted and not self.openFile(fileDialog.selectedFiles()[0])): @@ -75,7 +122,7 @@ class MainWindow(QMainWindow): file = QFile(fileName) if not file.exists(): nf = QDir.toNativeSeparators(fileName) - self.statusBar().showMessage(f"File {nf} could not be opened") + self.statusBar().showMessage(self.tr("File {} could not be opened").format(nf)) return False fileInfo = QFileInfo(file) @@ -87,7 +134,7 @@ class MainWindow(QMainWindow): self._viewer = self._factory.viewer(file) if not self._viewer: nf = QDir.toNativeSeparators(fileName) - self.statusBar().showMessage(f"File {nf} can't be opened.") + self.statusBar().showMessage(self.tr("File {} can't be opened.").format(nf)) return False self.ui.actionPrint.setEnabled(self._viewer.hasContent()) @@ -115,7 +162,7 @@ class MainWindow(QMainWindow): n = defaultViewer.viewerName() text += f"\n\nOther mime types will be displayed with {n}." - QMessageBox.about(self, "About Document Viewer Demo", text) + QMessageBox.about(self, self.tr("About Document Viewer Demo"), text) @Slot() def onActionAboutQtTriggered(self): diff --git a/examples/demos/documentviewer/mainwindow.ui b/examples/demos/documentviewer/mainwindow.ui index 3406d6ed..dd311ec2 100644 --- a/examples/demos/documentviewer/mainwindow.ui +++ b/examples/demos/documentviewer/mainwindow.ui @@ -70,7 +70,7 @@ 0 0 798 - 472 + 457 @@ -88,24 +88,32 @@ 0 0 983 - 26 + 24 - File + &File - + Help + + + Language + + + + + @@ -113,7 +121,7 @@ - toolBar + ToolBar TopToolBarArea @@ -131,11 +139,10 @@ - - :/demos/documentviewer/images/open.png:/demos/documentviewer/images/open.png + - Open + &Open Ctrl+O @@ -143,11 +150,10 @@ - - :/demos/documentviewer/images/help-about.svgz:/demos/documentviewer/images/help-about.svgz + - about documentviewer + About Document Viewer Show information about the Document Viewer deomo. @@ -158,11 +164,10 @@ - - :/demos/documentviewer/images/go-next.svgz:/demos/documentviewer/images/go-next.svgz + - actionForward + Forward One step forward @@ -173,11 +178,10 @@ - - :/demos/documentviewer/images/go-previous.svgz:/demos/documentviewer/images/go-previous.svgz + - actionBack + Back One step back @@ -191,11 +195,10 @@ false - - :/demos/documentviewer/images/print2x.png:/demos/documentviewer/images/print2x.png + - Print + &Print Print current file @@ -222,37 +225,49 @@ - - :/demos/documentviewer/images/document-open-recent.svgz:/demos/documentviewer/images/document-open-recent.svgz + - Recently opened... + &Recently opened... Meta+R - + - Quit + E&xit + + + E&xit - Quit the application + Exits the application Ctrl+Q + + + &English + + + + + &Deutsch + + - actionQuit + actionExit triggered() MainWindow close() diff --git a/examples/demos/documentviewer/pdfviewer/pdfviewer.py b/examples/demos/documentviewer/pdfviewer/pdfviewer.py index a2de67ad..c355a0d1 100644 --- a/examples/demos/documentviewer/pdfviewer/pdfviewer.py +++ b/examples/demos/documentviewer/pdfviewer/pdfviewer.py @@ -5,7 +5,7 @@ from __future__ import annotations from math import sqrt from PySide6.QtWidgets import (QListView, QTreeView) -from PySide6.QtGui import QIcon, QKeySequence, QPainter +from PySide6.QtGui import QAction, QIcon, QKeySequence, QPainter from PySide6.QtCore import (QDir, QIODevice, QModelIndex, QPointF, Slot) from PySide6.QtPrintSupport import QPrinter @@ -24,7 +24,6 @@ class PdfViewer(AbstractViewer): def __init__(self): super().__init__() self.uiInitialized.connect(self.initPdfViewer) - self._toolBar = None self._zoomSelector = None self._pageSelector = None self._document = None @@ -34,6 +33,22 @@ class PdfViewer(AbstractViewer): self._bookmarks = None self._pages = None + icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn, + QIcon(":/demos/documentviewer/images/zoom-in.png")) + self._actionZoomIn = QAction(self) + self._actionZoomIn.setIcon(icon) + self._actionZoomIn.setShortcut(QKeySequence.StandardKey.ZoomIn) + self._actionZoomIn.setToolTip(self.tr("Increase zoom level")) + self._actionZoomIn.triggered.connect(self.onActionZoomInTriggered) + + icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut, + QIcon(":/demos/documentviewer/images/zoom-out.png")) + self._actionZoomOut = QAction(self) + self._actionZoomOut.setIcon(icon) + self._actionZoomOut.setShortcut(QKeySequence.StandardKey.ZoomOut) + self._actionZoomOut.setToolTip(self.tr("Decrease zoom level")) + self._actionZoomOut.triggered.connect(self.onActionZoomOutTriggered) + def init(self, file, parent, mainWindow): self._pdfView = QPdfView(parent) super().init(file, self._pdfView, mainWindow) @@ -42,13 +57,41 @@ class PdfViewer(AbstractViewer): def supportedMimeTypes(self): return ["application/pdf"] + def cleanup(self): + super().cleanup() + del self._pageSelector + self._pageSelector = None + del self._zoomSelector + self._zoomSelector = None + del self._pages + self._pages = None + del self._bookmarks + self._bookmarks = None + del self._document + self._document = None + + def retranslate(self): + if not self._toolBars: + return + self._toolBars[0].setWindowTitle(self.tr("PDF")) + self._actionZoomIn.setText(self.tr("Zoom in")) + self._actionZoomIn.setToolTip(self.tr("Increase zoom level")) + self._actionZoomOut.setText(self.tr("Zoom out")) + self._actionZoomOut.setToolTip(self.tr("Decrease zoom level")) + index = self._uiAssets_tabs.indexOf(self._pages) + if index >= 0: + self._uiAssets_tabs.setTabText(index, self.tr("Pages")) + index = self._uiAssets_tabs.indexOf(self._bookmarks) + if index >= 0: + self._uiAssets_tabs.setTabText(index, self.tr("Bookmarks")) + def initPdfViewer(self): - self._toolBar = self.addToolBar("PDF") - self._zoomSelector = ZoomSelector(self._toolBar) + toolBar = self.addToolBar() + self._zoomSelector = ZoomSelector(toolBar) nav = self._pdfView.pageNavigator() - self._pageSelector = QPdfPageSelector(self._toolBar) - self._toolBar.insertWidget(self._uiAssets_forward, self._pageSelector) + self._pageSelector = QPdfPageSelector(toolBar) + toolBar.insertWidget(self._uiAssets_forward, self._pageSelector) self._pageSelector.setDocument(self._document) self._pageSelector.currentPageChanged.connect(self.pageSelected) nav.currentPageChanged.connect(self._pageSelector.setCurrentPage) @@ -58,20 +101,11 @@ class PdfViewer(AbstractViewer): self._uiAssets_back.triggered.connect(self.onActionBackTriggered) self._uiAssets_forward.triggered.connect(self.onActionForwardTriggered) - self._toolBar.addSeparator() - self._toolBar.addWidget(self._zoomSelector) - - icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn, - QIcon(":/demos/documentviewer/images/zoom-in.png")) - actionZoomIn = self._toolBar.addAction(icon, "Zoom in", QKeySequence.StandardKey.ZoomIn) - actionZoomIn.setToolTip("Increase zoom level") - actionZoomIn.triggered.connect(self.onActionZoomInTriggered) + toolBar.addSeparator() + toolBar.addWidget(self._zoomSelector) - icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut, - QIcon(":/demos/documentviewer/images/zoom-out.png")) - actionZoomOut = self._toolBar.addAction(icon, "Zoom out", QKeySequence.StandardKey.ZoomOut) - actionZoomOut.setToolTip("Decrease zoom level") - actionZoomOut.triggered.connect(self.onActionZoomOutTriggered) + toolBar.addAction(self._actionZoomIn) + toolBar.addAction(self._actionZoomOut) nav.backAvailableChanged.connect(self._actionBack.setEnabled) nav.forwardAvailableChanged.connect(self._actionForward.setEnabled) @@ -79,6 +113,7 @@ class PdfViewer(AbstractViewer): self._zoomSelector.zoomModeChanged.connect(self._pdfView.setZoomMode) self._zoomSelector.zoomFactorChanged.connect(self._pdfView.setZoomFactor) self._zoomSelector.reset() + self._pdfView.zoomFactorChanged.connect(self._zoomSelector.setZoomFactor) bookmarkModel = QPdfBookmarkModel(self) bookmarkModel.setDocument(self._document) @@ -99,8 +134,10 @@ class PdfViewer(AbstractViewer): self._pages.selectionModel().currentRowChanged.connect(self._currentRowChanged) self._pdfView.pageNavigator().currentPageChanged.connect(self._pageChanged) - self._uiAssets_tabs.addTab(self._pages, "Pages") - self._uiAssets_tabs.addTab(self._bookmarks, "Bookmarks") + self._uiAssets_tabs.addTab(self._pages, "") + self._uiAssets_tabs.addTab(self._bookmarks, "") + + self.retranslate() def viewerName(self): return "PdfViewer" @@ -131,12 +168,12 @@ class PdfViewer(AbstractViewer): documentTitle = self._document.metaData(QPdfDocument.MetaDataField.Title) if not documentTitle: - documentTitle = "PDF Viewer" + documentTitle = self.tr("PDF Viewer") self.statusMessage(documentTitle) self.pageSelected(0) file_name = QDir.toNativeSeparators(self._file.fileName()) - self.statusMessage(f"Opened PDF file {file_name}") + self.statusMessage(self.tr("Opened PDF file {}").format(file_name)) self.maybeEnablePrinting() def hasContent(self): diff --git a/examples/demos/documentviewer/pdfviewer/zoomselector.py b/examples/demos/documentviewer/pdfviewer/zoomselector.py index 70133d99..d55d5990 100644 --- a/examples/demos/documentviewer/pdfviewer/zoomselector.py +++ b/examples/demos/documentviewer/pdfviewer/zoomselector.py @@ -3,12 +3,11 @@ from __future__ import annotations from PySide6.QtWidgets import QComboBox -from PySide6.QtCore import Signal, Slot +from PySide6.QtCore import QEvent, QLocale, Signal, Slot from PySide6.QtPdfWidgets import QPdfView -ZOOM_LEVELS = ["Fit Width", "Fit Page", "12%", "25%", "33%", "50%", "66%", - "75%", "100%", "125%", "150%", "200%", "400%"] +ZOOM_LEVELS = [12, 25, 33, 50, 66, 75, 100, 125, 150, 200, 400] class ZoomSelector(QComboBox): @@ -17,14 +16,35 @@ class ZoomSelector(QComboBox): def __init__(self, parent): super().__init__(parent) + + self.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents) self.setEditable(True) - for z in ZOOM_LEVELS: - self.addItem(z) + # ZoomMode::FitToWidth, ZoomMode::FitInView + factors + for i in range(2 + len(ZOOM_LEVELS)): + self.addItem("") + + self.retranslate() self.currentTextChanged.connect(self.onCurrentTextChanged) self.lineEdit().editingFinished.connect(self._editingFinished) + def changeEvent(self, event): + if event.type() == QEvent.Type.LanguageChange: + self.retranslate() + super().changeEvent(event) + + def retranslate(self): + i = 0 + self.setItemText(i, self.tr("Fit Width")) + i += 1 + self.setItemText(i, self.tr("Fit Page")) + i += 1 + percent = QLocale().percent() + for z in ZOOM_LEVELS: + self.setItemText(i, f"{z}{percent}") + i += 1 + @Slot() def _editingFinished(self): self.onCurrentTextChanged(self.lineEdit().text()) @@ -40,13 +60,13 @@ class ZoomSelector(QComboBox): @Slot(str) def onCurrentTextChanged(self, text): - if text == "Fit Width": + if text == self.itemText(0): self.zoomModeChanged.emit(QPdfView.ZoomMode.FitToWidth) - elif text == "Fit Page": + elif text == self.itemText(1): self.zoomModeChanged.emit(QPdfView.ZoomMode.FitInView) else: factor = 1.0 - withoutPercent = text.replace('%', '') + withoutPercent = text.replace(QLocale().percent(), '') zoomLevel = int(withoutPercent) if zoomLevel: factor = zoomLevel / 100.0 diff --git a/examples/demos/documentviewer/rc_documentviewer.py b/examples/demos/documentviewer/rc_documentviewer.py index fee82431..00712ec2 100644 --- a/examples/demos/documentviewer/rc_documentviewer.py +++ b/examples/demos/documentviewer/rc_documentviewer.py @@ -1,11 +1,482 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.5.1 +# Created by: The Resource Compiler for Qt version 6.11.0 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore qt_resource_data = b"\ +\x00\x00\x1dG\ +<\ +\xb8d\x18\xca\xef\x9c\x95\xcd!\x1c\xbf`\xa1\xbd\xdd\xa7\ +\x00\x00\x00\x05de_DEB\x00\x00\x02\x88\x00\x00\ +T\x86\x00\x00\x12\xa4\x00\x04]\xc4\x00\x00\x14\x96\x00\x04\ +\x87\x9b\x00\x00\x0a(\x00\x04\xbb\x04\x00\x00\x17\x81\x00\x04\ +\xec0\x00\x00\x0d\xb4\x00\x05\x1a^\x00\x00\x06Y\x00\x05\ ++\xd4\x00\x00\x0d\xe1\x00\x14\xa8\xc4\x00\x00\x0c\xfa\x00*\ +\xa6y\x00\x00\x14+\x00*\xd0%\x00\x00\x00\x00\x00*\ +\xd0%\x00\x00\x083\x00+f\xbe\x00\x00\x08c\x00G\ +\xdf\x04\x00\x00\x0b\xe9\x00V}\xc3\x00\x00\x0f8\x00V\ +}\xc3\x00\x00\x13\x0f\x00X\xfd\xf4\x00\x00\x0f\xc5\x00e\ +c\x05\x00\x00\x19\xfb\x01/\xc9\x1e\x00\x00\x17\xb7\x01e\ +\xf6\x9e\x00\x00\x13>\x01\xe7Z\x93\x00\x00\x07\xed\x02\x02\ +<\x1e\x00\x00\x01\x9f\x02<^\x1b\x00\x00\x05p\x02{\ +\xf4\xd4\x00\x00\x18\x06\x02\xb0\xa6\x13\x00\x00\x06\x84\x02\xb6\ +\x8a\xa5\x00\x00\x14`\x02\xb7\x90T\x00\x00\x08\x95\x02\xb9\ +0\xad\x00\x00\x07=\x03\x93\xed~\x00\x00\x18Y\x03\xc8\ +?T\x00\x00\x0e\xee\x03\xda\xadN\x00\x00\x019\x04y\ +\x06^\x00\x00\x14\xd2\x04\xab\x8e\xf8\x00\x00\x0a\x95\x04\xab\ +\x8e\xf9\x00\x00\x0a\xc6\x04\xab\x8e\xff\x00\x00\x0a\xf7\x04\xab\ +\x8f\x00\x00\x00\x0b(\x04\xab\x8f\x01\x00\x00\x0bY\x05\x03\ +}\xc3\x00\x00\x03\xa0\x05<\xa4\x02\x00\x00\x0e\x0e\x05\xca\ +\x85B\x00\x00\x12\xcb\x05\xf2\xd7\xa4\x00\x00\x04\x5c\x06#\ +\xc2C\x00\x00\x05\xc7\x06#\xc2C\x00\x00\x0aW\x06#\ +\xc2C\x00\x00\x11m\x06*K\x9e\x00\x00\x02_\x061\ +X<\x00\x00\x05!\x06]\xb6\xa8\x00\x00\x1aF\x06_\ +,\xee\x00\x00\x04\x1e\x06_p\xd4\x00\x00\x13w\x06l\ +S<\x00\x00\x11\xf1\x06\x93EN\x00\x00\x02\xcb\x06\xb3\ +S\xc5\x00\x00\x0fh\x06\xc9\xa9L\x00\x00\x04\xd6\x06\xd0\ +\x0c_\x00\x00\x09\x84\x07;\xe7~\x00\x00\x15\x80\x08E\ +\xefn\x00\x00\x0c\x1d\x08s\xbe}\x00\x00\x03\x15\x08\xac\ +\x04\xdb\x00\x00\x06\x05\x09\x05\xab7\x00\x00\x01\x00\x09J\ +\x06b\x00\x00\x09$\x09^ >\x00\x00\x19$\x09l\ +a\xf4\x00\x00\x09\xf3\x0aa\xfdk\x00\x00\x0e?\x0a\xfa\ +{\x1e\x00\x00\x08\xca\x0bf\x08\xd2\x00\x00\x11+\x0c4\ +Ht\x00\x00\x06\xc9\x0c<\x99\x0e\x00\x00\x0f\xf5\x0cD\ +\xfe\x8e\x00\x00\x004\x0cRV\xae\x00\x00\x04\x9d\x0ca\ +\x9a\xcd\x00\x00\x03\xd2\x0c\xaf[\xb4\x00\x00\x00\xaf\x0c\xb2\ +9^\x00\x00\x10m\x0c\xbcS,\x00\x00\x11\xaa\x0c\xc6\ +~\x94\x00\x00\x0e\x93\x0d\x15\x15\x94\x00\x00\x16\x0a\x0di\ +\xd8\xc4\x00\x00\x0d~\x0e,}\x9e\x00\x00\x0c{\x0e6\ +\xd8~\x00\x00\x13\xb3\x0e\xb9\x80\x94\x00\x00\x16\xc2\x0f\xac\ +G\x81\x00\x00\x01\xf8\x0f\xb4\x18\x0f\x00\x00\x0b\x8a\x0f\xd0\ +<\xfd\x00\x00\x126i\x00\x00\x1a\x94\x03\x00\x00\x00\x0c\ +\x00&\x00D\x00a\x00t\x00e\x00i\x08\x00\x00\x00\ +\x00\x06\x00\x00\x00\x05&File\x07\x00\x00\x00\x0e\ +AbstractViewer\x01\x03\ +\x00\x00\x00D\x00K\x00e\x00i\x00n\x00 \x00I\ +\x00n\x00h\x00a\x00l\x00t\x00 \x00z\x00u\ +\x00m\x00 \x00D\x00r\x00u\x00c\x00k\x00e\ +\x00n\x00 \x00v\x00o\x00r\x00h\x00a\x00n\ +\x00d\x00e\x00n\x00.\x08\x00\x00\x00\x00\x06\x00\x00\ +\x00\x14No content to \ +print.\x07\x00\x00\x00\x0eAbstr\ +actViewer\x01\x03\x00\x00\x00 \x00\ +D\x00o\x00k\x00u\x00m\x00e\x00n\x00t\x00\ + \x00d\x00r\x00u\x00c\x00k\x00e\x00n\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x0ePrint D\ +ocument\x07\x00\x00\x00\x0eAbst\ +ractViewer\x01\x03\x00\x00\x00\x0e\ +\x00D\x00r\x00u\x00c\x00k\x00e\x00n\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00\x08Printing\ +\x07\x00\x00\x00\x0eAbstractVie\ +wer\x01\x03\x00\x00\x002\x00D\x00r\x00u\x00\ +c\x00k\x00v\x00o\x00r\x00g\x00a\x00n\x00\ +g\x00 \x00a\x00b\x00g\x00e\x00b\x00r\x00\ +o\x00c\x00h\x00e\x00n\x00.\x08\x00\x00\x00\x00\ +\x06\x00\x00\x00\x11Printing ab\ +orted.\x07\x00\x00\x00\x0eAbstr\ +actViewer\x01\x03\x00\x00\x00&\x00\ +D\x00r\x00u\x00c\x00k\x00v\x00o\x00r\x00\ +g\x00a\x00n\x00g\x00 \x00l\x00\xe4\x00u\x00\ +f\x00t\x00.\x08\x00\x00\x00\x00\x06\x00\x00\x00\x10P\ +rinting active.\x07\ +\x00\x00\x00\x0eAbstractView\ +er\x01\x03\x00\x00\x002\x00D\x00r\x00u\x00c\ +\x00k\x00v\x00o\x00r\x00g\x00a\x00n\x00g\ +\x00 \x00a\x00b\x00g\x00e\x00b\x00r\x00o\ +\x00c\x00h\x00e\x00n\x00!\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x12Printing can\ +celed!\x07\x00\x00\x00\x0eAbstr\ +actViewer\x01\x03\x00\x00\x006\x00\ +D\x00r\x00u\x00c\x00k\x00v\x00o\x00r\x00\ +g\x00a\x00n\x00g\x00 \x00a\x00b\x00g\x00\ +e\x00s\x00c\x00h\x00l\x00o\x00s\x00s\x00\ +e\x00n\x00.\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13P\ +rinting complete\ +d.\x07\x00\x00\x00\x0eAbstractV\ +iewer\x01\x03\x00\x00\x00\x18\x00D\x00r\x00\ +u\x00c\x00k\x00f\x00e\x00h\x00l\x00e\x00\ +r\x00.\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0fPri\ +nting error.\x07\x00\x00\x00\ +\x0eAbstractViewer\x01\ +\x03\x00\x00\x00T\x00D\x00i\x00e\x00 \x00D\x00\ +a\x00t\x00e\x00i\x00 \x00{\x00}\x00 \x00\ +k\x00a\x00n\x00n\x00 \x00n\x00i\x00c\x00\ +h\x00t\x00 \x00g\x00e\x00l\x00e\x00s\x00\ +e\x00n\x00 \x00w\x00e\x00r\x00d\x00e\x00\ +n\x00:\x00\x0a\x00{\x00}\x08\x00\x00\x00\x00\x06\x00\ +\x00\x00\x17Cannot read f\ +ile {}:\x0a{}\x07\x00\x00\x00\x0bI\ +mageViewer\x01\x03\x00\x00\x00\x0c\ +\x00B\x00i\x00l\x00d\x00e\x00r\x08\x00\x00\x00\ +\x00\x06\x00\x00\x00\x06Images\x07\x00\x00\x00\ +\x0bImageViewer\x01\x03\x00\x00\ +\x00\x22\x00Z\x00o\x00o\x00m\x00 \x00z\x00u\ +\x00r\x00\xfc\x00c\x00k\x00s\x00e\x00t\x00z\ +\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0aRe\ +set Zoom\x07\x00\x00\x00\x0bIma\ +geViewer\x01\x03\x00\x00\x00\x16\x00&\ +\x00V\x00e\x00r\x00g\x00r\x00\xf6\x00\xdf\x00e\ +\x00r\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x08Zo\ +om &In\x07\x00\x00\x00\x0bImage\ +Viewer\x01\x03\x00\x00\x00\x18\x00&\x00V\ +\x00e\x00r\x00k\x00l\x00e\x00i\x00n\x00e\ +\x00r\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x09Zo\ +om &Out\x07\x00\x00\x00\x0bImag\ +eViewer\x01\x03\x00\x00\x00\x12\x00u\x00\ +n\x00b\x00e\x00k\x00a\x00n\x00n\x00t\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x07unknown\ +\x07\x00\x00\x00\x0bImageViewer\ +\x01\x03\x00\x00\x00 \x00&\x00+\x00A\x00l\x00l\ +\x00e\x00 \x00e\x00r\x00w\x00e\x00i\x00t\ +\x00e\x00r\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0c\ +&+Expand all\x07\x00\x00\x00\ +\x0aJsonViewer\x01\x03\x00\x00\x00\ +\x22\x00&\x00-\x00A\x00l\x00l\x00e\x00 \x00\ +r\x00e\x00d\x00u\x00z\x00i\x00e\x00r\x00\ +e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0e&-C\ +ollapse all\x07\x00\x00\x00\x0a\ +JsonViewer\x01\x03\x00\x00\x00,\ +\x00L\x00e\x00s\x00e\x00z\x00e\x00i\x00c\ +\x00h\x00e\x00n\x00 \x00h\x00i\x00n\x00z\ +\x00u\x00f\x00\xfc\x00g\x00e\x00n\x08\x00\x00\x00\ +\x00\x06\x00\x00\x00\x0cAdd bookma\ +rk\x07\x00\x00\x00\x0aJsonViewe\ +r\x01\x03\x00\x00\x00\x16\x00L\x00e\x00s\x00e\x00\ +z\x00e\x00i\x00c\x00h\x00e\x00n\x08\x00\x00\ +\x00\x00\x06\x00\x00\x00\x09Bookmarks\ +\x07\x00\x00\x00\x0aJsonViewer\x01\ +\x03\x00\x00\x00&\x00L\x00e\x00s\x00e\x00z\x00\ +e\x00i\x00c\x00h\x00e\x00n\x00 \x00l\x00\ +\xf6\x00s\x00c\x00h\x00e\x00n\x08\x00\x00\x00\x00\ +\x06\x00\x00\x00\x0fDelete book\ +mark\x07\x00\x00\x00\x0aJsonVie\ +wer\x01\x03\x00\x00\x00\x08\x00J\x00s\x00o\x00\ +n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04Json\x07\ +\x00\x00\x00\x0aJsonViewer\x01\x03\ +\x00\x00\x00\x1a\x00J\x00s\x00o\x00n\x00-\x00A\ +\x00k\x00t\x00i\x00o\x00n\x00e\x00n\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00\x0cJson Act\ +ions\x07\x00\x00\x00\x0aJsonVie\ +wer\x01\x03\x00\x00\x00>\x00J\x00s\x00o\x00\ +n\x00-\x00D\x00o\x00k\x00u\x00m\x00e\x00\ +n\x00t\x00 \x00{\x00}\x00 \x00w\x00u\x00\ +r\x00d\x00e\x00 \x00g\x00e\x00\xf6\x00f\x00\ +f\x00n\x00e\x00t\x08\x00\x00\x00\x00\x06\x00\x00\x00\ +\x17Json document {\ +} opened\x07\x00\x00\x00\x0aJso\ +nViewer\x01\x03\x00\x00\x00h\x00J\x00\ +s\x00o\x00n\x00-\x00D\x00o\x00k\x00u\x00\ +m\x00e\x00n\x00t\x00 \x00a\x00u\x00s\x00\ + \x00{\x00}\x00 \x00k\x00o\x00n\x00n\x00\ +t\x00e\x00 \x00n\x00i\x00c\x00h\x00t\x00\ + \x00g\x00e\x00p\x00a\x00r\x00s\x00t\x00\ + \x00w\x00e\x00r\x00d\x00e\x00n\x00:\x00\ + \x00{\x00}\x08\x00\x00\x00\x00\x06\x00\x00\x00)U\ +nable to parse J\ +son document fro\ +m {}: {}\x07\x00\x00\x00\x0aJso\ +nViewer\x01\x03\x00\x00\x00\x18\x00l\x00\ +e\x00t\x00z\x00t\x00e\x00 \x00D\x00a\x00\ +t\x00e\x00i\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0f%\ +n recent files\x07\x00\ +\x00\x00\x0aMainWindow\x01\x03\x00\ +\x00\x00\x0c\x00&\x00D\x00a\x00t\x00e\x00i\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x05&File\x07\x00\ +\x00\x00\x0aMainWindow\x01\x03\x00\ +\x00\x00\x0e\x00&\x00\xd6\x00f\x00f\x00n\x00e\x00\ +n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05&Open\ +\x07\x00\x00\x00\x0aMainWindow\x01\ +\x03\x00\x00\x00\x10\x00&\x00D\x00r\x00u\x00c\x00\ +k\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06&\ +Print\x07\x00\x00\x00\x0aMainWi\ +ndow\x01\x03\x00\x00\x00(\x00&\x00Z\x00u\ +\x00l\x00e\x00t\x00z\x00t\x00 \x00g\x00e\ +\x00\xf6\x00f\x00f\x00n\x00e\x00t\x00.\x00.\ +\x00.\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13&Rec\ +ently opened...\x07\ +\x00\x00\x00\x0aMainWindow\x01\x03\ +\x00\x00\x00,\x00\xdc\x00b\x00e\x00r\x00 \x00D\ +\x00o\x00k\x00u\x00m\x00e\x00n\x00t\x00e\ +\x00n\x00a\x00n\x00z\x00e\x00i\x00g\x00e\ +\x08\x00\x00\x00\x00\x06\x00\x00\x00\x15About \ +Document Viewer\x07\ +\x00\x00\x00\x0aMainWindow\x01\x03\ +\x00\x00\x006\x00\xdc\x00b\x00e\x00r\x00 \x00D\ +\x00o\x00k\x00u\x00m\x00e\x00n\x00t\x00e\ +\x00n\x00a\x00n\x00z\x00e\x00i\x00g\x00e\ +\x00-\x00D\x00e\x00m\x00o\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x1aAbout Docume\ +nt Viewer Demo\x07\x00\ +\x00\x00\x0aMainWindow\x01\x03\x00\ +\x00\x00\x0e\x00\xdc\x00b\x00e\x00r\x00 \x00Q\x00\ +t\x08\x00\x00\x00\x00\x06\x00\x00\x00\x08About\ + Qt\x07\x00\x00\x00\x0aMainWind\ +ow\x01\x03\x00\x00\x00\x0c\x00Z\x00u\x00r\x00\xfc\ +\x00c\x00k\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04Ba\ +ck\x07\x00\x00\x00\x0aMainWindo\ +w\x01\x03\x00\x00\x00\x16\x00L\x00e\x00s\x00e\x00\ +z\x00e\x00i\x00c\x00h\x00e\x00n\x08\x00\x00\ +\x00\x00\x06\x00\x00\x00\x09Bookmarks\ +\x07\x00\x00\x00\x0aMainWindow\x01\ +\x03\x00\x00\x00\x0c\x00S\x00t\x00r\x00g\x00+\x00\ +H\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06Ctrl+\ +H\x07\x00\x00\x00\x0aMainWindow\ +\x01\x03\x00\x00\x00\x0c\x00S\x00t\x00r\x00g\x00+\ +\x00I\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06Ctrl\ ++I\x07\x00\x00\x00\x0aMainWindo\ +w\x01\x03\x00\x00\x00\x0c\x00S\x00t\x00r\x00g\x00\ ++\x00O\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06Ctr\ +l+O\x07\x00\x00\x00\x0aMainWind\ +ow\x01\x03\x00\x00\x00\x0c\x00S\x00t\x00r\x00g\ +\x00+\x00P\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06Ct\ +rl+P\x07\x00\x00\x00\x0aMainWin\ +dow\x01\x03\x00\x00\x00\x0c\x00S\x00t\x00r\x00\ +g\x00+\x00Q\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06C\ +trl+Q\x07\x00\x00\x00\x0aMainWi\ +ndow\x01\x03\x00\x00\x00,\x00D\x00o\x00k\ +\x00u\x00m\x00e\x00n\x00t\x00e\x00n\x00a\ +\x00n\x00z\x00e\x00i\x00g\x00e\x00-\x00D\ +\x00e\x00m\x00o\x08\x00\x00\x00\x00\x06\x00\x00\x00\x14\ +Document Viewer \ +Demo\x07\x00\x00\x00\x0aMainWin\ +dow\x01\x03\x00\x00\x00\x10\x00&\x00B\x00e\x00\ +e\x00n\x00d\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\ +\x00\x00\x05E&xit\x07\x00\x00\x00\x0aMai\ +nWindow\x01\x03\x00\x00\x00*\x00B\x00\ +e\x00e\x00n\x00d\x00e\x00t\x00 \x00d\x00\ +i\x00e\x00 \x00A\x00n\x00w\x00e\x00n\x00\ +d\x00u\x00n\x00g\x08\x00\x00\x00\x00\x06\x00\x00\x00\ +\x15Exits the appli\ +cation\x07\x00\x00\x00\x0aMainW\ +indow\x01\x03\x00\x00\x00H\x00D\x00a\x00\ +t\x00e\x00i\x00 \x00{\x00}\x00 \x00k\x00\ +a\x00n\x00n\x00 \x00n\x00i\x00c\x00h\x00\ +t\x00 \x00g\x00e\x00\xf6\x00f\x00f\x00n\x00\ +e\x00t\x00 \x00w\x00e\x00r\x00d\x00e\x00\ +n\x00.\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18Fil\ +e {} can't be op\ +ened.\x07\x00\x00\x00\x0aMainWi\ +ndow\x01\x03\x00\x00\x00J\x00D\x00a\x00t\ +\x00e\x00i\x00 \x00{\x00}\x00 \x00k\x00o\ +\x00n\x00n\x00t\x00e\x00 \x00n\x00i\x00c\ +\x00h\x00t\x00 \x00g\x00e\x00\xf6\x00f\x00f\ +\x00n\x00e\x00t\x00 \x00w\x00e\x00r\x00d\ +\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1bFi\ +le {} could not \ +be opened\x07\x00\x00\x00\x0aMa\ +inWindow\x01\x03\x00\x00\x00\x10\x00V\ +\x00o\x00r\x00w\x00\xe4\x00r\x00t\x00s\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00\x07Forward\x07\ +\x00\x00\x00\x0aMainWindow\x01\x03\ +\x00\x00\x00\x0a\x00H\x00i\x00l\x00f\x00e\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00\x04Help\x07\x00\x00\x00\ +\x0aMainWindow\x01\x03\x00\x00\x00\ +\x0a\x00L\x00i\x00n\x00k\x00s\x08\x00\x00\x00\x00\ +\x06\x00\x00\x00\x04Left\x07\x00\x00\x00\x0aMa\ +inWindow\x01\x03\x00\x00\x00\x0c\x00M\ +\x00e\x00t\x00a\x00+\x00R\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x06Meta+R\x07\x00\x00\x00\x0aM\ +ainWindow\x01\x03\x00\x00\x00(\x00\ +E\x00i\x00n\x00e\x00n\x00 \x00S\x00c\x00\ +h\x00r\x00i\x00t\x00t\x00 \x00z\x00u\x00\ +r\x00\xfc\x00c\x00k\x08\x00\x00\x00\x00\x06\x00\x00\x00\ +\x0dOne step back\x07\x00\ +\x00\x00\x0aMainWindow\x01\x03\x00\ +\x00\x00,\x00E\x00i\x00n\x00e\x00n\x00 \x00\ +S\x00c\x00h\x00r\x00i\x00t\x00t\x00 \x00\ +v\x00o\x00r\x00w\x00\xe4\x00r\x00t\x00s\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x10One ste\ +p forward\x07\x00\x00\x00\x0aMa\ +inWindow\x01\x03\x00\x00\x00\x1e\x00D\ +\x00o\x00k\x00u\x00m\x00e\x00n\x00t\x00 \ +\x00\xf6\x00f\x00f\x00n\x00e\x00n\x08\x00\x00\x00\ +\x00\x06\x00\x00\x00\x0dOpen Docum\ +ent\x07\x00\x00\x00\x0aMainWind\ +ow\x01\x03\x00\x00\x00\x0c\x00S\x00e\x00i\x00t\ +\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05Pa\ +ges\x07\x00\x00\x00\x0aMainWind\ +ow\x01\x03\x00\x00\x00,\x00A\x00k\x00t\x00u\ +\x00e\x00l\x00l\x00e\x00 \x00D\x00a\x00t\ +\x00e\x00i\x00 \x00d\x00r\x00u\x00c\x00k\ +\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x12Pr\ +int current file\ +\x07\x00\x00\x00\x0aMainWindow\x01\ +\x03\x00\x00\x00\x0c\x00R\x00e\x00c\x00h\x00t\x00\ +s\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05Right\ +\x07\x00\x00\x00\x0aMainWindow\x01\ +\x03\x00\x00\x00>\x00Q\x00t\x00-\x00L\x00i\x00\ +z\x00e\x00n\x00z\x00i\x00n\x00f\x00o\x00\ +r\x00m\x00a\x00t\x00i\x00o\x00n\x00e\x00\ +n\x00 \x00a\x00n\x00z\x00e\x00i\x00g\x00\ +e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1bSho\ +w Qt license inf\ +ormation\x07\x00\x00\x00\x0aMai\ +nWindow\x01\x03\x00\x00\x00n\x00I\x00\ +n\x00f\x00o\x00r\x00m\x00a\x00t\x00i\x00\ +o\x00n\x00e\x00n\x00 \x00\xfc\x00b\x00e\x00\ +r\x00 \x00d\x00i\x00e\x00 \x00D\x00o\x00\ +k\x00u\x00m\x00e\x00n\x00t\x00e\x00n\x00\ +a\x00n\x00z\x00e\x00i\x00g\x00e\x00-\x00\ +D\x00e\x00m\x00o\x00 \x00a\x00n\x00z\x00\ +e\x00i\x00g\x00e\x00n\x00.\x08\x00\x00\x00\x00\ +\x06\x00\x00\x001Show inform\ +ation about the \ +Document Viewer \ +deomo.\x07\x00\x00\x00\x0aMainW\ +indow\x01\x03\x00\x00\x00\x1c\x00W\x00e\x00\ +r\x00k\x00z\x00e\x00u\x00g\x00l\x00e\x00\ +i\x00s\x00t\x00e\x08\x00\x00\x00\x00\x06\x00\x00\x00\ +\x07ToolBar\x07\x00\x00\x00\x0aMai\ +nWindow\x01\x03\x00\x00\x00\x16\x00L\x00\ +e\x00s\x00e\x00z\x00e\x00i\x00c\x00h\x00\ +e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x09Boo\ +kmarks\x07\x00\x00\x00\x09PdfVi\ +ewer\x01\x03\x00\x00\x00\x16\x00V\x00e\x00r\ +\x00k\x00l\x00e\x00i\x00n\x00e\x00r\x00n\ +\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13Decrea\ +se zoom level\x07\x00\x00\ +\x00\x09PdfViewer\x01\x03\x00\x00\x00\ +\x14\x00V\x00e\x00r\x00g\x00r\x00\xf6\x00\xdf\x00\ +e\x00r\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13I\ +ncrease zoom lev\ +el\x07\x00\x00\x00\x09PdfViewer\ +\x01\x03\x00\x00\x00>\x00D\x00i\x00e\x00 \x00P\ +\x00D\x00F\x00-\x00D\x00a\x00t\x00e\x00i\ +\x00 \x00{\x00}\x00 \x00w\x00u\x00r\x00d\ +\x00e\x00 \x00g\x00e\x00\xf6\x00f\x00f\x00n\ +\x00e\x00t\x08\x00\x00\x00\x00\x06\x00\x00\x00\x12Op\ +ened PDF file {}\ +\x07\x00\x00\x00\x09PdfViewer\x01\x03\ +\x00\x00\x00\x06\x00P\x00D\x00F\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x03PDF\x07\x00\x00\x00\x09PdfV\ +iewer\x01\x03\x00\x00\x00\x1c\x00P\x00D\x00\ +F\x00-\x00B\x00e\x00t\x00r\x00a\x00c\x00\ +h\x00t\x00e\x00r\x08\x00\x00\x00\x00\x06\x00\x00\x00\ +\x0aPDF Viewer\x07\x00\x00\x00\x09\ +PdfViewer\x01\x03\x00\x00\x00\x0c\x00\ +S\x00e\x00i\x00t\x00e\x00n\x08\x00\x00\x00\x00\ +\x06\x00\x00\x00\x05Pages\x07\x00\x00\x00\x09P\ +dfViewer\x01\x03\x00\x00\x00\x14\x00V\ +\x00e\x00r\x00g\x00r\x00\xf6\x00\xdf\x00e\x00r\ +\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07Zoom\ + in\x07\x00\x00\x00\x09PdfViewe\ +r\x01\x03\x00\x00\x00\x16\x00V\x00e\x00r\x00k\x00\ +l\x00e\x00i\x00n\x00e\x00r\x00n\x08\x00\x00\ +\x00\x00\x06\x00\x00\x00\x08Zoom out\x07\ +\x00\x00\x00\x09PdfViewer\x01\x03\x00\ +\x00\x00D\x00<\x00k\x00e\x00i\x00n\x00e\x00\ + \x00z\x00u\x00l\x00e\x00t\x00z\x00t\x00\ + \x00g\x00e\x00\xf6\x00f\x00f\x00n\x00e\x00\ +t\x00e\x00n\x00 \x00D\x00a\x00t\x00e\x00\ +i\x00e\x00n\x00>\x08\x00\x00\x00\x00\x06\x00\x00\x00\ +\x11\x07\x00\x00\x00\x0eRecentFil\ +eMenu\x01\x03\x00\x00\x00\x12\x00&\x00K\x00\ +o\x00p\x00i\x00e\x00r\x00e\x00n\x08\x00\x00\ +\x00\x00\x06\x00\x00\x00\x05&Copy\x07\x00\x00\x00\ +\x09TxtViewer\x01\x03\x00\x00\x00\x12\ +\x00&\x00E\x00i\x00n\x00f\x00\xfc\x00g\x00e\ +\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06&Pas\ +te\x07\x00\x00\x00\x09TxtViewer\ +\x01\x03\x00\x00\x00\x1a\x00&\x00A\x00u\x00s\x00s\ +\x00c\x00h\x00n\x00e\x00i\x00d\x00e\x00n\ +\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04C&ut\x07\x00\ +\x00\x00\x09TxtViewer\x01\x03\x00\x00\ +\x00l\x00D\x00a\x00t\x00e\x00i\x00 \x00{\ +\x00}\x00 \x00k\x00a\x00n\x00n\x00 \x00n\ +\x00i\x00c\x00h\x00t\x00 \x00z\x00u\x00m\ +\x00 \x00S\x00c\x00h\x00r\x00e\x00i\x00b\ +\x00e\x00n\x00 \x00g\x00e\x00\xf6\x00f\x00f\ +\x00n\x00e\x00t\x00 \x00w\x00e\x00r\x00d\ +\x00e\x00n\x00:\x00\x0a\x00{\x00}\x00.\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00$Cannot o\ +pen file {} for \ +writing:\x0a{}.\x07\x00\x00\x00\ +\x09TxtViewer\x01\x03\x00\x00\x00T\ +\x00D\x00i\x00e\x00 \x00D\x00a\x00t\x00e\ +\x00i\x00 \x00{\x00}\x00 \x00k\x00a\x00n\ +\x00n\x00 \x00n\x00i\x00c\x00h\x00t\x00 \ +\x00g\x00e\x00l\x00e\x00s\x00e\x00n\x00 \ +\x00w\x00e\x00r\x00d\x00e\x00n\x00:\x00\x0a\ +\x00{\x00}\x08\x00\x00\x00\x00\x06\x00\x00\x00\x18Ca\ +nnot read file {\ +}:\x0a{}.\x07\x00\x00\x00\x09TxtVi\ +ewer\x01\x03\x00\x00\x00d\x00A\x00u\x00s\ +\x00g\x00e\x00w\x00\xe4\x00h\x00l\x00t\x00e\ +\x00n\x00 \x00I\x00n\x00h\x00a\x00l\x00t\ +\x00 \x00i\x00n\x00 \x00d\x00i\x00e\x00 \ +\x00Z\x00w\x00i\x00s\x00c\x00h\x00e\x00n\ +\x00a\x00b\x00l\x00a\x00g\x00e\x00 \x00k\ +\x00o\x00p\x00i\x00e\x00r\x00e\x00n\x08\x00\ +\x00\x00\x00\x06\x00\x00\x006Copy the\ + current selecti\ +on's contents to\ + the clipboard\x07\x00\ +\x00\x00\x09TxtViewer\x01\x03\x00\x00\ +\x00l\x00A\x00u\x00s\x00g\x00e\x00w\x00\xe4\ +\x00h\x00l\x00t\x00e\x00n\x00 \x00I\x00n\ +\x00h\x00a\x00l\x00t\x00 \x00i\x00n\x00 \ +\x00d\x00i\x00e\x00 \x00Z\x00w\x00i\x00s\ +\x00c\x00h\x00e\x00n\x00a\x00b\x00l\x00a\ +\x00g\x00e\x00 \x00a\x00u\x00s\x00s\x00c\ +\x00h\x00n\x00e\x00i\x00d\x00e\x00n\x08\x00\ +\x00\x00\x00\x06\x00\x00\x005Cut the \ +current selectio\ +n's contents to \ +the clipboard\x07\x00\x00\ +\x00\x09TxtViewer\x01\x03\x00\x00\x00\ +\x14\x00B\x00e\x00a\x00r\x00b\x00e\x00i\x00\ +t\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04E\ +dit\x07\x00\x00\x00\x09TxtViewe\ +r\x01\x03\x00\x00\x00\x22\x00D\x00a\x00t\x00e\x00\ +i\x00 \x00{\x00}\x00 \x00g\x00e\x00l\x00\ +a\x00d\x00e\x00n\x00.\x08\x00\x00\x00\x00\x06\x00\ +\x00\x00\x0fFile {} loade\ +d.\x07\x00\x00\x00\x09TxtViewer\ +\x01\x03\x00\x00\x00(\x00D\x00a\x00t\x00e\x00i\ +\x00 \x00{\x00}\x00 \x00g\x00e\x00s\x00p\ +\x00e\x00i\x00c\x00h\x00e\x00r\x00t\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00\x0dFile {} \ +saved\x07\x00\x00\x00\x09TxtVie\ +wer\x01\x03\x00\x00\x00t\x00I\x00n\x00h\x00\ +a\x00l\x00t\x00 \x00d\x00e\x00r\x00 \x00\ +Z\x00w\x00i\x00s\x00c\x00h\x00e\x00n\x00\ +a\x00b\x00l\x00a\x00g\x00e\x00 \x00i\x00\ +n\x00 \x00d\x00i\x00e\x00 \x00a\x00k\x00\ +t\x00u\x00e\x00l\x00l\x00e\x00 \x00A\x00\ +u\x00s\x00w\x00a\x00h\x00l\x00 \x00e\x00\ +i\x00n\x00f\x00\xfc\x00g\x00e\x00n\x08\x00\x00\ +\x00\x00\x06\x00\x00\x009Paste the\ + clipboard's con\ +tents into the c\ +urrent selection\ +\x07\x00\x00\x00\x09TxtViewer\x01\x03\ +\x00\x00\x00\x86\x00M\x00I\x00M\x00E\x00-\x00T\ +\x00y\x00p\x00 \x00{\x00}\x00 \x00w\x00i\ +\x00r\x00d\x00 \x00n\x00i\x00c\x00h\x00t\ +\x00 \x00u\x00n\x00t\x00e\x00r\x00s\x00t\ +\x00\xfc\x00t\x00z\x00t\x00.\x00 \x00E\x00s\ +\x00 \x00w\x00i\x00r\x00d\x00 \x00a\x00u\ +\x00f\x00 \x00{\x00}\x00 \x00z\x00u\x00r\ +\x00\xfc\x00c\x00k\x00g\x00e\x00g\x00r\x00i\ +\x00f\x00f\x00e\x00n\x00.\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00/Mime type {}\ + not supported. \ +Falling back to \ +{}.\x07\x00\x00\x00\x0dViewerFa\ +ctory\x01\x03\x00\x00\x00\x22\x00A\x00n\x00\ + \x00S\x00e\x00i\x00t\x00e\x00 \x00a\x00\ +n\x00p\x00a\x00s\x00s\x00e\x00n\x08\x00\x00\ +\x00\x00\x06\x00\x00\x00\x08Fit Page\x07\ +\x00\x00\x00\x0cZoomSelector\ +\x01\x03\x00\x00\x00$\x00A\x00n\x00 \x00B\x00r\ +\x00e\x00i\x00t\x00e\x00 \x00a\x00n\x00p\ +\x00a\x00s\x00s\x00e\x00n\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x09Fit Width\x07\x00\x00\ +\x00\x0cZoomSelector\x01\x88\ +\x00\x00\x00\x02\x01\x01\ \x00\x00\x0a\xde\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -182,1379 +653,6 @@ tEXtdate:modify\x00\ amp\x002023-03-09T0\ 7:09:01+00:00J\x90\xfc\ \x80\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x03\xb2\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xa7\ -PLTE\xff\xff\xff\xfe\xfe\xfe\xf6\xf6\xf6\xdc\xde\xe0\ -\xca\xcd\xcf\xc6\xc8\xcb\xce\xcf\xd1\xe5\xe6\xe6\xfc\xfc\xfc\xe7\ -\xe8\xe9\xbc\xc2\xca\xc0\xc8\xd3\xd4\xda\xe3\xd7\xdd\xe6\xc8\xcf\ -\xd9\xae\xb5\xbf\xc2\xc5\xc7\xf6\xf6\xf5\xea\xeb\xeb\xb7\xbf\xcb\ -\xce\xda\xe9\xe6\xef\xfb\xe9\xf2\xfc\xdd\xe7\xf4\xaf\xb9\xc7\xbe\ -\xc1\xc3\xfb\xfb\xfb\xfb\xfb\xfa\xbf\xc5\xcc\xbb\xcb\xe1\xd4\xe2\ -\xf2\xd1\xdd\xe9\xec\xf6\xfd\xee\xf8\xfe\xda\xe6\xef\xd5\xe1\xee\ -\xd3\xe1\xf2\xa8\xb2\xbf\xe8\xe8\xe8\xee\xee\xed\xa1\xaf\xc4\xbc\ -\xd2\xee\xd1\xe0\xef\xc9\xd7\xe3\xec\xf5\xf8\xef\xf7\xfa\xd7\xe2\ -\xea\xd3\xe0\xea\xe3\xef\xfc\xad\xbe\xd4\xcf\xd1\xd4\xe4\xe4\xe3\ -\x98\xab\xc4\xb9\xd3\xf1\xd7\xea\xf6\xd4\xe3\xec\xe7\xf1\xf3\xec\ -\xf3\xf6\xe3\xee\xf4\xdb\xe9\xf1\xe0\xf1\xfc\xa5\xba\xd6\xad\xb2\ -\xb6\xff\xfe\xfe\xe7\xe7\xe6\x9c\xac\xc2\xbd\xd7\xf2\xdc\xee\xf7\ -\xd6\xe4\xec\xe9\xf1\xf4\xed\xf4\xf6\xe4\xee\xf4\xdd\xea\xf1\xdf\ -\xf2\xfd\x9f\xb4\xce\xab\xae\xb0\xbe\xc5\xcf\xc3\xd7\xef\xe1\xf3\ -\xfb\xeb\xf5\xf9\xf1\xf7\xf8\xf4\xf8\xf9\xf3\xf9\xfb\xed\xf7\xfa\ -\xdc\xef\xfb\x91\xa0\xb2\xc9\xca\xc9\xdb\xdc\xdd\xa3\xb4\xc8\xce\ -\xe4\xf6\xf2\xfe\xff\xfa\xff\xff\xfe\xff\xff\xfb\xff\xff\xf1\xfd\ -\xff\xba\xcb\xdc\x98\x9e\xa2\xf4\xf3\xf3\xf9\xf9\xf8\xaf\xb3\xb5\ -\x90\xa0\xb1\xc5\xd8\xe7\xe8\xf4\xfb\xf0\xf9\xfd\xe4\xef\xf6\xb8\ -\xc5\xd1\x94\x9a\x9f\xe2\xe2\xe1\xf9\xf9\xf9\xc4\xc5\xc4\x96\x9d\ -\xa1\x96\xa0\xa8\x9d\xa6\xad\x9d\xa4\xa8\xa2\xa4\xa5\xb7\xb8\xb6\ -\xde\xdf\xde\xf8\xf8\xf7\xe9\xe9\xe8\xe6\xe6\xe5\xf3\xf3\xf2\xe5\ -\xe5\xe4\xd3\xd3\xd3\xaa\xab\xa9\xed\xed\xed\xcc\xcc\xcc\xc5\xc5\ -\xc4\xb6\xb7\xb6\xd5\xd5\xd5\xd2\xd2\xd1\xad\xae\xac\xd9\xda\xd9\ -\xf5\xf5\xf5\xc9\xc9\xc9\xca\xcb\xca\xb3\xb4\xb3\xca\xca\xca\xaf\ -\xb0\xae\xd5\xd6\xd4\xfa\xfa\xfa\xfd\xfd\xfd\x08\xe8\xba\x00\x00\ -\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\x09\ -pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\ -\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\ -\x1f\xc9N\xa7\x00\x00\x00\xcfIDAT\x18\xd3c`\ -\x80\x01F\x06t\xc0\xc4\xcc\xc2\xca\xc6\xce\x81\x22\xc6\xc9\ -\xc5\xcd\xc3\xcb\xc7/ \x88$&$,\x22*&&\ -&.!)\x05\x17\x93\x96\x91\x95\x93WPTRV\ -QU\x83\x0b\xaakhji\xeb\xe8\xea\xe9\x1b\x18\x1a\ -\xc1\x05\x8dML\xcd\xcc-,\xad\xacml\xed\xeca\ -\x82\x0e\x8eN\xce.\xaen\xee\x1e\x9e^\xde\x08\xf7\xf8\ -\xf8\xfa\xf9\x07\x04\x06\x05\x87\x84\x86!l\x0f\x8f\x88\x8c\ -\x8a\x8e\x89\x8d\x8bOHD\x08&%\xa7\xa4\xa6\xa5g\ -dfe#;>'7/\xbf\xa0\xb0\xa8\xb8\x04\xd5\ -\xaf\xa5e\xe5\x15\x95U\xd5\x0e\x18\x01PS[W\xcf\ -\x81!\xda\xd0\xd8\xd4\x8c!\xd8\xd2\xda\xd6\xc4\x84!Z\ -R\xdb\xde\x86!(\xd5\xd1\xd6\x89\xa9\xb4\xa6\xb9\xab\x1b\ -C\x90\xa1\x07\xe4R\x00\x9f\xbe*\xa3\xfa_u\xfe\x00\ -\x00\x00%tEXtdate:crea\ -te\x002023-03-08T17\ -:27:26+00:00\x9e\x98\xa5\x85\ -\x00\x00\x00%tEXtdate:mod\ -ify\x002023-03-08T1\ -7:27:26+00:00\xef\xc5\x1d\ -9\x00\x00\x00(tEXtdate:ti\ -mestamp\x002023-03-\ -09T06:20:50+00:0\ -0\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\xaeB`\ -\x82\ -\x00\x00\x15a\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed]ms\x1b\xb9\x91\ -\xfe\x9e_\xc1\xd3~\x89*$\xd4\xefhh\xd7I\xe5\ -\xbc\x95T\xaa\x9c\xca\xd5%\xa9\xbbo)Z\x1c\xd9\xba\ -H\xa2\x8a\xa4\xd6\xd6\xfe\xfa\x03\x86/C\x884)\xca\ -\x92\x15o<\x96\x8a\x83\x07\x8d\x97~\xd0\xdd\xc0@\xc4\ -\xf8\x87\xdf}\xbc\xba\xec\xfd\xd4L\xa6\x17\xe3\xebWG\ -\x18\xe0\xa8\xd7\x5c\x9f\x8dG\x17\xd7\xef^\x1d\xfd\xfdo\ -\x7f\x18\xf8Qo:\x1b^\x8f\x86\x97\xe3\xeb\xe6\xd5\xd1\ -\xf5\xf8\xe8w\xbf\xfd\xd5\x0f\xff1\x18\xf4^O\x9a\xe1\ -\xac\x19\xf5>\x5c\xcc\xde\xf7\xfet\xfd\xcf\xe9\xd9\xf0\xa6\ -\xe9\xfd\xfa\xfdlvszr\xf2\xe1\xc3\x87p\xb1\x00\ -\xc3x\xf2\xee\xe4\xb87\x18\xe4\x92\xd3\x9f\xde\xfd\xaa\xd7\ -\xeb\xe5f\xaf\xa7\xa7\xa3\xb3WG\x0b\xf9\x9b\xdb\xc9e\ -+7:;i.\x9b\xab\xe6z6=\xc1\x80'G\ -\x9d\xf8Y'~V\x1a\xbf\xf8\xa99\x1b_]\x8d\xaf\ -\xa7m\xc9\xeb\xe9wk\xc2\x93\xd1y\x96\xee:\xf3\x81\ -[!L)\x9d\x00\x9d\x10\x0d\xb2\xc4`zw=\x1b\ -~\x1c\xd4Es\x1f\xb7\x15%\x008\xc9y\x9d\xe4\xc3\ -\xa4N?^f&>\xd9\x996w\xbd\xf5\xcc\xfeM\ -\xfe]\x15X\x02a:\xbe\x9d\x9c5\xe7\xb9d\x13\xae\ -\x9b\xd9\xc9\x8f\x7f\xfbq\x959\x800\x9a\x8d\xd6\xaaY\ -\x92_\xb5[\x8d\xc8\xf5\xf0\xaa\x99\xde\x0c\xcf\x9a\xe9\xc9\ -\x12/\xe5\xf3\x80\x8ef\xef\xb31\x90\xb7\xc9\xf7\xcd\xc5\ -\xbb\xf7\xb3.}1zu\x94\xf5#\x03k\xd3\xcb\x1e\ -\x9c\xae\xcc\x08\x02S\x9b\xb5\xacv=K\xf47\xa3\xe6\ -\xa7\xe6\xb2\x08T\x96WW6\x1a\x9f\xe5\xfe\xe5\xde\xbf\ -\x1b\x0f\xae\x9b\x8f\xb3\x90\x9b\xfc\xb9\xaet|;\xbb\xb9\ -\x9d\xfd#g6\xd7\xf3Z\xb2V\x9d\x8am\xf6\xbc\xdc\ -\x0a\xdch\xe3\xedp\x9a\xdb8y?\xbejNn.\ -\xae\xb3\xae\x93\xf1\xc9p2\xfb0\x9e\xfc\xf3\xe4/\x1f\ -\xef\xde5\xd7'\xd3\xb3\xc9\xc5\xdb\xdbi\xfe\x1c^\x0e\ -\xdf^6u/\x9a\x8f7\xe3\xc9lp~q\xd9\xe4\ -\x0eoT\x96o\xce\xa6'\xe3\xb6\xa6U\x0d'\xc3\xb3\ -Y\xee\xf0\xf4d\xa9\xdc\xcd\xf5\xbb\xad\xb5~\x1c\xddd\ -;@\x87\xad\xb9w\xab\xdc\xdf\xe6\xec\x1fF\xcd\xf94\ -\x7f.\x86\xa8\xa4\xc8\xc0\xdb\xbc\x9c\x9b\xad\xac\x19N\xfe\ -8\x19\x8e.\xb2o\xb5\xd8z\x95g\xe3\xcb\xcb\xe6,\ -\x0f\xf3\xf0\xf2\xc3\xf0nZ\xda[UU\x17e\x91\xd4\ -V\xdaV;\x9d\x8do\x16\xf7\x85\xda\xd9\xdde\xa6\xa0\ -\x80\x83\x5c\xe3xr\xfa\x1d\xb4\xd7\xf7-4\xce\xd6v\ -1\xbb;\xc5\xef\x8f\xba2\xe3\xf3\xf3i\x93\x1b\x86\x16\ -\xebZ-%X\x14\x8fz'\x9f\xd7\x1alk\x0d\xb7\ -\xb7\xc6\xab\xd6~8\xa9\xd5\xdeM\xe3&K\x94\xf4\x00\ -\x96\x8c\x9bt~\xfeX\x96(\xc5\x83X:o\xaf\xc7\ -\xb2D)\xed`\xe9\x89\x8d\x8d\x0c\x8e\x0eT\xec\xf14\ -\x1a}I\x1aM\x9e\xce\xd8\x80\x0e`\xe9l\xd44\x9f\ -\xc1\x12\xc8A,\x8d\xa8\xf1f\xf4h\x96\xc0\xf6\xb1T\ -R\xc3\xcbm,\xfd\xef\x9f\xdf\xfc\xe9\xc7\x7f\xc8?V\ -\x15\x9f}|u$\xde%\xef^\x1d\x0d \x10\xaea\ -\x93WG\xaaa\x1dy\xb7\xa8\xf9o\x93\xe1\xf54\xcf\ -\xbeW\xaf\x8e\xae\x86\xb3\xc9\xc5\xc7_CH1Q\x1f\ -\xf2\xbfrK\xdaG\xe6\x00\x00\xd4'\x08\x1e\x8d\x8e7\ -j\xf9\xfb\xf5\xc5,/\x1cn\xa7\xcd\xe4\xafe\xf2\xfd\ -\xcb\xf5\xdf\xa7M=x\xdb\x06b'\xc5\x91~D\xfe\ -q\x1b}\xe0\xc5\xa4\xf7\xd5\x1eX\x95w7\xc1\xfa\xfb\ -\xd7\xf8z{\x13\xf2\x90&\x0c\x93\xecn\x02\xfe\x90\x14\ -|{\x13\xf6\x90&\xa2\xc6}M\x80\xbf\x06\xd8\xde\x84\ -\xefo\x02\xf7\xd5\x1e\x7f\xff\x89\xda\x13\xacYqe\xb0\ -\xb5\x15\x1f\x1c2\xdb\xd5\xe3\xe9\xfbI\x93W\xbb\xdfU\ -\xf6\xbe\xec@]1\x1b\xa5\x07\xda\xe4~\xfb\xc7\xd6\xf4\ -1 ;'J\xf9~\x80\x10\x14\xa3k<\xae\x9c\x0e\ -#U^\x17m\x95<\xbf\x97}^g\x17\x7f,\xa3\ -\xff\x844\xd5Q\xc4\xf2\xb5\x8b0B\xfe\x17$\xcc=\ -\xb8c\xe4\x8a6$\x0c\x22\x84\xfc)\xf6:\xcd\x9f\x8a\ -=&\x83]\xf37\x03v\xd5`\xd6\x83C\x142\xf6\ -\x15z\x97Q\xd4\xc0l*\xd2\xc9R\x96\xf5\x9081\ -\xa7N6\xa3h!*D=\xd4\x8c\xa77\x93f8\ -\xfas3{?\xce\xdd\xcc\xba\x14U;j\xf2\x02~\ -\xd6L\x1eNI;s \x10D@]\xc0\xdd\xb3S\ - \x10\x14\xe0\xae\xe7sy\x92\xa4\xd6Q\xd2=\x5c\x85\ -\x9c\x93\x90**\xe7]b\xf6\xd8\xcd\x0f\xe7\xcd\x1f\x87\ -\xb7\xd3\xe9\xc5\xf0\xfa?/oKw\xf7t\xb8\x8aX\ -\xa3\x1f\x9b\x9f.\x86\xb3\xe5\x93\x98\x98\xaa\xa5\xfb\xd3n\ -\xdd\x02\xb3\xaf\xaf\xf6\xe6]zFkJ\xba\xd3\x9a\x12\ -?\xc5\xa8\xd7\x16)!jm\x8d\x12(\xaa\xfbA\xd6\ -\xf8\x9cNf\x02\xb2\x8b\x16\x81C\x9d\xa1(\x8eI\xc2\ -=\xc5\x89C\x02s\xae\x14G\x90@Zi\xad\x01U\ -\x93:= \xe6\x0dp\xb1@rC\x10\xe3>\xb1\xe5\ -\xb0\x17\x84\x0d\xdd\xe2\xf1\xe7\xba jd`\xd9\xe2\x82\ -\x8cb\x107\x5c0*'\xb5m.\xa8\x0a1\xe16\ -\x1f\x14\xe1g\xf1A&\x95\x84\xea\xbb}PD7|\ -\xf0\xd9\xe6C\x16I\xbb\xe6CQ\xadf\xaaH\x01\x08\ -\x1c\xb9\x9a\xaf\xccB\xe96t\xb2\xe7[e\xcf\xb7\xca\ -N2\x08\x0f\xb1-\x08\xeah\xe4\xda\xc7\x10-\xe6\x9f\ -f@}\x0a\x96\xdcHRI@p\x8f\x9e\x9c\xfa\xc9\ -\x02DG\x95\xbe\x86$\x98 \xc9\x03\xd7\xe7\xcf;\x83\ -\x02\xedrn%|\xc2\xf5G\xed\x8a\x03\x04\x0fh\xc8\ -^y\xe4\xc3\x83'\x11\x07\xa6\x88T\xc5\x11\xe5\x10]\ -P\xeb8Bd\xc1\xb4\x0e\x9f\x1a\x03\xc5DL\x99\xe1\ -\x97\x9aU\x88\xc8\x1e\x13>c\x0a\x18\x89d#\x84F\ -u\x93:\x84F\x0e\xc9!b\xa5\xbci\x10q\xc4\x17\ -V\xde_V\xf9]f;+\xb7\x97\xc3Y\xf3\xeb\x14\ -\xfbp\xfc\x92nH\xca\x15\x03\x12C2\x97H5\x03\ -\x16\x90]1V\x0cH\x0a\x84\xb5lF]\x83P\xd4\ -C\x16\xb2\xfby*\x1113\xf5\x92K~\x05yb\ -\x8d\x08II\xad?\x80\x05\xbb\xe9\xb8\x1a\x0b\x8fA\xcc\ -\xc5\xea\x87\x8a,\xcd\x86\xe2\xa4V\xdb#Z\x88e8\ -\xea\x85\x1cX;\x1c\xf6\xa2\xd4\x91}a\xea0\xd9\x9c\ -\xbb\x8a:\xe3\xb9\xb4V\xc4)\x05\xc2\x9afjgo\ -\x22\x11\x8d/J\x1c\xc3\xbf\xc7$\xf9b\xcb>ey\ -2\x86\x8b]\xaa\x9bA,v\xa9\x84$Q\xca\xadS\ -\x14\x16\xc9\xe4\x0b\x82\x01b\x1f\x8d\x83r\x12\xed+\x04\ -P\xd5\xa4\xf5F\xc9\x00-\x98\xb9x\xbd\xad\xeb\x16\x9c\ -,Y\xb5\xfe\xdc&|^\x09W\x0b\xd0=t\x17!\ -\xe1 Q\xa2QU\x9fb\xd0D)a\xd5\xb8{\x00\ -!\x87To\xecHH1\x09Z\xbd\xa8\x8eA\x999\ -\xe2~3\xdb\xcf\xb5$CP+\xac\x0a$\xa7T\xa8\ -\x8el\xa0i\xc1\xba\x83Yy(\xc3\x10\xc9\x9c\xb5\xef\ -1x\x8a\xa0\xfe\xc0\x05\xf2.\xcbAN\x0f1@J\ -\x09\x8e\xf6Yr7$g\x97\x177\xff5\x9c\xbd_\ -ox\x891r7\x94Kp\xcf\xc6\xfbM\x16\xd9\xd8\ -\xdd\xcd\x8fZ\x97\xa7\xb7\x93\xcb_\x7f\xb7\xa9\xd3\xf1\xf7\ -%w\xed\x0f(\xd3\xd9d\xfc\xcf\xe6\xf4z|\xdd,\ -\xee\x07\xed\xb3\xe8)Z\x90\xa4\x00\xe0\xb2\xcc(\xaag\ -\xfdN'\xe3\xdb\xeb\xd1:\xf8\x7f\xe3\x8b\xeb\x1a\xbd\xba\ -\x985\x93\xcb\x8b\xfcq\xba*>\x1aN\xdf\x0f'\x93\ -\xe1]\xd5ZA\xdb\xad\xea,\x19P\x17p\xd7\xc3\xb5\ -\x87\xccL\xd6\x9f{\x83\x14\x03\x13:\xf6)\x86$\x1c\ -\xb5\xf7\xba7@\x88A\x0d\x91V(f\x10c\x88\x09\ -\x81\xfb\x8c\xc1\xd11Z\x06I\x83\xaa\x1a\xf59\x05u\ -\x8aFm\x05\xcc!\x83.}\x89\x19N)q\x01c\ -\x10\x11\xb7\xbez \x22\xc3\x05\x88\xa9\xc4X\xf3\x10c\ -\x12H\xbd7\xebp*\x8eI X\xea\xed`\x04\x08\ -\x1e9\xfa\xbc\xb1$\x80\x05\x94\x80\xe4T@J\xc1\xdd\ -9u`\xa9\xd7$DUb\xed\xe0R\xafA\x88\xc0\ -\x92\xd6\xe1\x81\x96\xeezJ\xbaj\xac\x02\xbb\x8e\xbdY\ -\x87;5^\xb70&\x8f\xa6+\x95)7\x86\x01\x1c\ -b\xd4ur,e\xd0\x01\xb0&2Zp%u\x5c\ -\x91\x9eG\xc2c\x96%Iq9<\x92\xb6\x0d\xe4\xcf\ -\xbd\xf5\xf1\x15\x0e\xe2\x08(\xa5\xda\x04!9\x89\xfa\x0a\ -\xd6\x5c\xad\x04c\xd6\xcc\x8dhH\x18Sn>\x85h\ -\x82\xd1J\xe8\x8d\x1eI\xad\xed\x95\x84\xa4\x94\x15P\x0d\ -\x9a\xdc3\x89\x92\xac\xdc\xba\x1bs\x09\x19\ -\x09\x83\x88\x11\xf7\x8d\x02)'\xb0\xcf\x9f@H\xe09\ -\x1e@\xea\xc2\xeb\xcf\xeb\x83\x12Y\x8d\x08\xeb}P\x83\ - @`Zo\xb5H\x80h(k]\xc4J\xf6@\ -\xed77.\xfdA\xda\x9bEy\xb4\xf6\x87u\xb2p\ -\xe4\x155\x03\x83\x8a\x14\x04\x0d\xce\xa0R\x91\x92\xa5B\ -\x12%\xc5\x9d\xfa\x0a?lG\x89\xe3\x93\x8c6a\x00\ -F\xd2J!\xc6\xc0\xd1,z\xa5V\xcc\xa2.\x09\xbc\ -R\x0bQC'\xbbwS\x0f\x021\x13Q\xeacH\ -\xa8\x90\x04\x8f\x9f\xc0J\xf8A\xac\x99\x88=\x09kb\ -\x81\xc8]SE\x9b\xa6\x8c\xaaC\xaci\x0b\x9e\x22\x13\ -\xc6\x8a\xb6N\xf6\xa1\xb4\x19\xb1e\xda\x9e\x86.\xfc\xa2\ -t9\x05\xa0\xe4ZG\x94(A$F\xa3z\xdf\x9b\ -,$6#\xabC\x8a|\xb6\xda\xe4h\x87\xab\xfd\xfc\ -\xb1\xc4\x831\x8b\xd7\xe4`\x0a\x89\x85\xd9*r\x14\x82\ -:\x98T\xb6Td!Y\x8c\xbaCy3\x87/\x12\ -H\xf7\x9br\x9f\x8f\x8f\x0e\xa7I!D\xf2\xe4u\xa4\ -2\x0a\xc0~\x7fs\x1a5$\xe4\x88\x15K\x9d\xe8.\ -\x96\xe4\x81\xd3\x8d\xf0\x174\x91(E\x1f\x94X\x87\x1b\ -\x0dL\x09\x98k\xdd\x91*\xb5;\xa9]js|\x98\ -\xda\x8cO3\xebx \xd6tO!\xe4\x90\x0cA*\ -}\x08\x03\x02\xa6$\x9d\xe8\x1c\x85$\x19}\xec\xfa\xae\ -\x0e\xae\x82\x85\x22\xeaC0L\xac\xe2\x9f\x1fdM\x22\ -=4\xda|\xe3\xf4\xc1\x9c\xc2\x97\xe54\x06\x88\x00)\ -\xd5\x9cj@2w\xff\x85\x90j\xf2%H\xdd\xaf\xde\ - \x06\xf5\x08\xc9\xbc\x9fo\xd9\xc1D\xe9\xf8\x00\xe2v\ -\xa9_\x0f*DW\xd9\x18TO\xc8|\xc8\xa0\xee\xa4\ -U\xe9_p\xb51\xa0Zm\xaa#\x83{\xa5(\xd3\ -N\x0d\xf1\x81\xde\x08\xf2\x10\x0d\xf7\xee\x15PPM\xe2\ -V\x7fk\x1c8\xa8\x03w\xe0\xc7V4\x02\xf0\xda\x08\ -\x9cm\x13=\xabD\xb7\xdb\xd9\xcdp\xf4\x90\xbf\xba\x07\ -N\xa6\xa4\xdc\xb7\x90\x9cQ\x14\x9b\x01\x173\x8e\x8e\x92\ -\xac$0\xa8\x1a:k\xbf\xfd{\x81\x93!\xf6\x07\x96\ -\x82%7|\x82\xad\x82d\x89\x0e<3p\xb8\xc9\xed\ -\xb2\x07\x90\xfa(\xd0\xe6Y\x12\x13\xb0\xa3\x83O\x08\x81\ -c\xc3\xcd\xc6\x91\xaf\xde\xc9\xfe\xd6\xfc\xe8\xe0\x830\xec\ -q\xd4\xc8FkOt\xda\xcdDl?K\xe2\x87\xb3\ -t\xde^\x8faIa\x1bK\x87\xb7\x06O\xc7\x921\ -\xeee\xc9\x98\x0fg)\x02G\x80G\xb0d\xac\x87\xb3\ -\xf4\xf6\x5c\x1b\x80\xe7cIx?K\xa2\x8f\xb2\xa5\xd1\ -\xe8\xec1,I|\x8c-\x8d\xce\xdf>\xa3-\x99\x1d\ -p\xbe4\x9d\x9f\xe19=\xf2|\xa9\x99\xf9~\x96\x18\ -}\xab\xe5\x06\xdd\xcd\x93zl\xde\xea\xbeQ\xd9\x1dJ\ -\xcfh\xa8\xfe\x00\xedp\xabv\x11\x9enT\xa2\xec\xb7\ -\xdd\xf8\x98\xd9\x02\xd0\xc4\x1fc\xbb\xf1\x11\xb3\x05\xe20\ -=_\x1cd\xe2\xb8\x8f%&N\x87\xb34\xd2\xc6\x1e\ -3[0\x09\xfe\xab\xcd\x16\x94\x12\xecc)\xcb\xd0#\ -X\x8a\xe5\xdf\xe1,QJ\xf24,\xe1\x0e\x96\x0e?\ -xT\x9f\x0b\xc2g:\x9b\xa7\xa6\xa4N{\xce\x05\xe1\ -\x96\xb3y/v\x08\x8d\xf0\xc9\x0e\x16o\x9e\x19\xe3\xa8\ -\xd5\x97o\x9f\xf8\x1c\xdb\x8b~\xb3\x96\x08_\xe2\x9b\xb5\ -\x0c\xf4b\xdf\xac}\xc9\xaf\x8a\x13=\xc9\x19\xd2\xe7:\ -\xd5\xfc\xa2g\x5cH\xbf}\x8b\xfe\x99}=\x1e\xc0\xf0\ -S\x1e~$\xd6\x00\xd1\xd1\xaa\xc3\x8f_\xe2\x1c\xe7\x0b\ -\x9enc\xa2\xf4ox`|\x7f\x0cd\xfa\xe6\xe8\xcf\ -\xfb\xd6\x10\x96\xaf\xff\xad!_\xf8\xd54\xc4\xf6\xf5\xbd\ -\x9a\xe6E\xa3\x1b\xfb\x81\x84\xed?c\xfb\x0b<\xe3\xcc\ -$\xf0K>\xe0\x0d\xb4[yzJ#A\xf6\xf6\x84\ -\xf1\xf3\x1c\x86~\xe1iQ\xe4\xa9\xa9\x8a\x1e\xc5\x7f\xf9\ -\x07\x97I\xecy\x99{\xfes\xcb?\x9c\x94W\xa6\xb6\ -w\xdd\xe9\x8d\xe1U3\xfa\xe9\xa2\xf9\xd0\xbdW\xf5\xed\ -p\xa5\xc4\xcd\xf0]\xd3\xee;e\xba\xce\xdbk\x91\xf1\ -v<\x195\x93e\x96\xb5W\x95\xb5\xd8\x9aZ\xbc\xf7\ -\xb6\x1e\x9dR\xeb*\x1f\xb6\xe7O\xdf\x0fG\xe3\x0fy\ -\xf9s?\xf3\xe7\xf1\xf8\xaa\x94J\x8b\xeb~~\x99\xf0\ -X,$&\xd6\x8d\xcc\xdc\xa0\x041aOr?s\ -4>\xbb-oe\x1e\xdc\xce\x87\xf5\xe6\xe3F\xf1\xdb\ -\xc9\xa4\x08\x5c\x0e\xef\x9a\xacw\xfb\x81E\xa8z\xa7p\ -)W\xbdw\xa7\xc3\x8aQ\x5c\x8c\xa6E\xf7\xebw\xb9\ -\xa3\xb2\x0d\xbf\xbb\x8f7W7\x8b\xac\x8e\x8f\xe9\xfb\xf1\ -\x87\x9cWv\xcc\x86\x97\xd3\xe6~OK\xd6\xe0f|\ -q]\x14\x99Mn\x9b\xf5b\xb7\xb9\xca\x0e\xae\xcb\x95\ -\xbc\xc1\xdb\xb7\xe3\x8f\xdb\xf3?\x5c\x5c\xe7a\x19,\x95\ -\x05\xb4OH,u\xb7D\x9f\x90(\xe3d\xf0\x89\xcc\ -\xcc\x81\xc5\xe5\x8b}+\xad\x96\x06>\xbb+o~\xfe\ -xW\xb0\xcao\x0b@\x1am\x05Vt\xdfG\xefj\ -\xb4\xa2z\xe56\x9b\xde\xd2\xe2W\xcdl8\x1a\xce\x86\ -\x9d\xeb,\x112\xc4e\xef'\xa3\xf3\xd3\xff\xfe\xf1\x0f\ -\xab=\xd4\xb3\xb3\xd3\xff\xc9\xef^\xee\xb6=\x8b\xc0\xf0\ -\xed\xf86\xb3\xd5\x96Y\xc8\x8d\xceNK\xd0\x18\xce~\ -{q\x95\x1d\xa2\xbcq\xfb7\xf9\xc5\xd7\xd9\x89\x97\x19\ -\xb5p!$\x03U\xb5\x93f\xfeF\xedm/!\xcf\ -?W\x17\xa5\xd0\xc9_g\xf9\x10\xc9\x9fJ#\xeb\x9b\ -\xd6'\x8b\x8e.7_;=rb\xa9h\x9bzw\ -o\x0c\xf3\x1b\xa0\x9b\xcbWGo.\xf2K\xb0/\xc7\ -=\xdc4\xce\xf1\xed\xcd\xd5x\xd4,\x5ch\x99?Z\ -\xa4\x0bu\xdd\x89\xb3\x1d\xe7\xcd\xca\x19\xb3\xee\xaa\xac\xa0\ -\x14\xa5\xc5^]w\xd2L%DG\xd2>R@S\ -\x11\xcb\x07\x81\x90(\x888\xb4\x07~Pb\xf4r:\ -\x09\x89\x830J\x01]\x19\x942\x94S\x14<9\x88\ -\xcdSE\xb2\xbd\xd1\x80\x80Ie\xad\x9c\x05\x14K\xe2\ -]\x03\x05s%\xc2\xd2\xacJ\x80\xc4Q\xfb\x88\x1a\x9c\ -\x941\xd7%)$\x05\xe3\x0cz\x10\xd5\xd8\x13i\xef\ -\x9d\x5ch\x9e\xe0\x5c8\xdf\xb8\x07(\x97\xb4*t\xc9\ -\x5cK\x0a\x861\xd9\x023\xf5^,\xf1\x92\x04\xb8\xef\ -\x16@\x11\x14{\x8b\x02\x05+utI)ut%\ -\x04C\x12O\xba\xaa\x96S\xc9c\x91\xd2\xae@\xdb\x9d\ -\xd5'\xea\x224\x17m\xa4%\x1a\xdc\x8d{\x8a\x01\x08\ -\x94\xfa)\x88X\x04\xf5-\xc3\xd1\x1d\x80\xda<\xfe\xb4\ -\xf1J\xb8\xcd\x03\xeamrr{\xd9\x9c6?5\xd9\ -PF\x9f>\x10\xe5\xc1\x089\xba\xa7\xc5\xe5\xfa\x8c\x07\ -\xa3\xea\x13POc\xde\xacL\xdf\xcc\xfb+7\xef\xa5\ -Y@ \x15s4\xf9\xfe\x93\x07\xfe\x94\xe5),>\ -\xbd\x9c\x9d\xef\xf7j\x06\xfc\x1c\x1d\xf1\xe6\xe3}\xed\xde\ -\xde\xcef\x1b\xca\xb5\xfa,\xd0\xba\xa5\x8c\xcf;\xd6\xbd\ -\x9b\xf4\xb8\xf6\xb2H\xa1Xu\x00#-\x96\x84A\x88\ -\x12R\xc1\x90bt\xeeE\x08\xec\x06)\x15L8\x91\ -[\xcfR)F\xbd7\xa5\x84\x17\xc7H\xc1Hs\xda\ -\xa1\x8f\xedZ?\xaeR\xb9\xde\xf2\xc9\x01\x1cM\xbc\x17\ --\x80!\xc4\x5c!\x04\x173\xab\xbb\xf1\xf3ft\xa0\ -H+p\xf6\xa9\xbf?b\xbf\xb87\x1c\xd7\x83\xb5\x19\ -i0\xd5\x1c\x14\xa76\xc5\xb9\x83\xbcn\x93\x8ch^\ -\xbc\x09U\xd40c\xa5\xf7nY\xc4\x02\x92n\x02o\ -2\x94\x82\xf3\x02*\xe1\xa2\x06\x8b\xa7.\x92\x89\xbd\xf3\ -TD\x0fD`\x95\xaf\xa2.J\x15W\xad;\xf8s\ -\xef\xd3ng(\xc4D\xdf\x7f\xd2\x22\x95\xf0+\xf0\xba\ -]&,\xc2\xc7\xfbM!\x7f y\xf9-\x83s\xfc\ -@\xf7\xdd|u\xe6WD\xd6\xb7\xd9\xf3+\x9c=\xeb\ -\xc8$\x12\x8f\x0eXC\xed\x0es\x9c\xf0+\x0a\xf5\x9b\ -\xb1,\x82 &\x8f\x9f\x0ef\x9c\xf8_nz\xdd\x19\ -\x9b 8\xa3\x09\xd2*F!\x04e\xe46PE\x17\ -\xd4\xe3\x9d\x06\xb0k\xe07\xdb\xa3\xe0\xec\x84\xec\xe5O\ -\xcb\x03(\xd6\xaa)R\xbf\xbb\xab%X\x03\xa2\x83x\ -\x1f\xd5\x83\xb8)\x1f\xef\x18\x1fI`l\xbac\xb2a\ -\xf8%L6_\xffZ\xa1^\xfe\x90w\xe0\xd6\xff\xc0\ -*\xc1'\x05\xee\x16\x02\x9d\x09\xbe{\xe8ljt\xd0\ -;\xd8\x9ee\xd0\xf7/\x1c \xb8Ar\xd3\xee\xbd\xe7\ -\x92b\xb4\xe3z[\x8e%\xd2A/\x13\xaa\x1f}7\ -\x02\x7f\x94\x02W\x86\xe6\x18TA\xa4O\x18\xc4$I\ -;\xb81(\xa8\xc4~\xe4\xe0`ISk(\x16\x10\ -\x93eT\x83\xa0)`\x8b\xb1\xa0\xf6\xa3\x05'G\x8e\ -ma\x01\x06\xeeGo\xa3\xb2a\xa9\xb2\xc4kJ\x01\ -\xa2\x9b\x95\xca\x5c\xfa\xc8\x14\xcc\xc0s\xc2\xdb\x84+z\ -jS\x9d\xe8\x9b6\x9dR J\x18)\xa7\x89d#\ -\xad\x14b\x04\xe7\x85\xfcF\x9a8$J\xc4T\x9a.\ -i\x08\x91\x9d\xb5\xb4&\xc1%\x22\x97\x09\x87T\xc8y\ -\x93\x94\xc3^2C\xb81a\x048h\xca\xb0/\xfd\ -\xb8y\xf2n\x97\x9f\x95\xc2\xdb}j[@&\x22;\ -\xbe\xa7P\x80\x18#\xa4\xe8i~\xbdd\xb4E\x8f\xc7\ -\x87x(3\xcf\x0dz\x97\x9fZzR?\xc5\xfb~\ -J\xd17\xfd\x94P\x02n\xf8)!\x85X\xfbi\x8b\ -\xa5\xdaOKa\xd8\xf0S\x8a\xb1\xf2Sr\xec\xfc\x94\ -\x5c;?-\xa9\xcaOs\xba\xf2K&\xbc\x9f\xae\xfc\ -\x92\x5c\xef\xa7+?-\xe9\xceO\xc9q\xc3OkR\ -j?\xddo\xc2\x9b\x1e\xb9\xdb\xa8\xfdSF\x9d^\xc0\ -[\x0f\xdf%\x22\xfd\xbce\xac\xbd\xc8\xf3%\x84d\xed\ -\xd3\x80\x05T#\x9d?\xd2\x91\x00\x01\x15\x90!r\x94\ -\x9ex\x1fc\x10N\xe8\xde&\xd2B\xfcMI\xc9\xfc\ -\x91n\xf5i\xba\xaa\x8c0$N\x02\xa9o\x16\xd8@\ -\x15{\xe4A\xb4\x08\xf4-\x06(\x17\xf68\x05F\xd2\ -\x8c\xe4Bf\xc5\x02Y\xa5$\x13\x84\xe4\x1as\x05\x1c\ -\x80\x1c\x84{\x88\x108wZ-\x08\xcc{\xa1\x12\xa4\ -@\x18\x030\xb6M+\x07\x02$\xc1V\x0f\x8c\x92\xb4\ -\xa7\x14\x00\xd4c*\x18\x98j\xd4\x0d\x0e\xb6\xed\x5c\x11\ -1\xed_\xd2\x97\xc7\xba\xc7m1*\xc8\xe6\x94\xf6\x95\ -\x99\x0fF\xf4xYJ\xf6U\xed\xb4\xb4On\ -?\xe4\xaf\xa5\xe5\x8f\xff\x07K$K_f\x83\x00\x00\ -\ -\x00\x00\x1b\x90\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed=io\x1b9\x96\ -\x9f'\xbfB\xeb\xfe\xb2\xc1J\x14\xef\xc3\x1dg\xe0\xb3\ -\xd1@\x0ff\xb0\x99c\xbf\x05\x8aTv4-K\x86\ -$'v\xff\xfa}\x8fu\x97\x8aeQ)w\xecA\ -+\x08,\xb1\xc8G\xf2\xdd\x8f|E\xbe\xfb\xf3\xc3\xed\ -b\xf0%Yo\xe6\xab\xe5\xc9\x11#\xf4h\x90,\xa7\ -\xab\xd9|ysr\xf4\x8f\xbf_\x8d\xec\xd1`\xb3\x9d\ -,g\x93\xc5j\x99\x9c\x1c-WG\x7f~\xff\xe6\xdd\ -\x7f\x8dF\x83\x9f\x92e\xb2\x9elW\xeb\xe3\xc1\xe9l\ -\xf5)\x19\xfc\xbcX\xdco\xb6\xbeh\xc08\xa1\x84\x0e\ -\x07\x1f\xfe\xf9\xd3\xe0\xf2\xe1n\xb5\xde\x0e\xfe\xb6\xb8\xbf\ -\x19\xfd\xbc\x1c\x10_\xf8\xcf\xb4\xcf\xe3\x81&\x94\x0e\xce\ -\xee\xe7\x8b\xd9@1)\xed\xdb\xc1`4z\xff\xe6\xcd\ -\xbb\xcd\x97\x9b7\x83\xc1\x00\x06\xb8\xdc\x1c/7'G\ -\x9f\xb7\xdb\xbb\xe3\xf1x\xb9!\x13\xec\x8fLW\xb7\xe3\ -\x0f\x93/\xc9\xd5j\xfd\xaf\xe4\xd3\x18\x06?>*[\ -\xcc\xa6E\x8b\xbb\xfb\xf5\x82\xac\xd67\xe3\xd9t\x9c,\ -\x92\xdbd\xb9\xdd@uV\xad>-\xabO\xd7\xc9d\ -;\xff\x92\x00\xfc\xdb\x15\xf4\x86-\x97\x9b\x1f*\x95\xd7\ -\xb3\xeb\xa2\xf6\xd7\xaf_\xc9W\xe1+1\xe7\xdc\x98\xf2\ -1\xe7#\xa81\xda<.\xb7\x93\x87Q\xbd)\xcc\xaa\ -\xad)\xa7\x94\x8e\xe1YYs\xbfZ\xc7\x0f\x8b\xf9\xf2\ -\xd7\xe0`\xfc\xd3j\xef@\xd9;\xf8_4\xc8\x0b\xc8\ -fu\xbf\x9e&\xd7\xd02!\xcbd;\xbe\xf8\xfbE\ -\xf1pD\xc9l;\xab\x80\x01\xa0\x9b\xe9\xe4.\xa9\xf5\ -\x9b\x17\xa6\xf8\x9a\xdc&\x9b\xbb\xc94\xd9\x8c\xf3r\xdf\ -\xbe\xc6jX0\x9f\x9d\x1c\xfd\x02\xc8^,V\x1f\x99\ -/\xf9:\x9fm?C\x05n\xfd\xcf\xcf\xc9\xfc\xe6\xf3\ -\xb6\xfc\xfde\x9e|=[=\x9c\x1c\xd1\x01\x056c\ -\xf0?\x85\xb4\x02\xd8\xd7\x8b\xd5\xd7\x93\xa3/\xf3\xcd\xfc\ -\xd3\x22\xed0YN\xe0\xeb\xe8\xd3d\xfa\xeb\xcdzu\ -\xbf\x84\xee\x96\xc9\xd7A\xb31\xcc\xeb\xd8\x8f\xf7\xe4\xe8\ -n\x9dl\x92\xf5\x97\xb4y\x8e\x82\xe3b\xe0\x94\x08\x9e\ -\x8e<\x9bW\xf5\x91\xb4\x84\x0d\xd6\xcehZo=[\ -M\x11#'G\xb39\x08\xd2\xcdh\xbe\x04L\xdf\x02\ -\x97\xad\x96\x04\xe8\xf9[\x1d\xe0\xea~{w\xbf\xfd\x98\ -\xfd\xb6\ -\xd9\xb3\xa6p\x94\xa3c\xbb\xa3\xeb\x9c\xfd\xb5\xff\xec0\ -Ix\xfa\xcc\xc5M\xbf\xad\x83o\x9a>\xb7\xda\x1e=\ -!8\xed\x83\xe7\xd6\xd08\xecP:\x95\xd3\xe9\xbe\xbc\ -\x01\xf0y\x0f\xc8\xf9\xf1[\xb0\xa3\x99\xc8\xb1s\x93\x15\ -\xfdc9\xdf\x82\xdfv\x0f\xee\xc3\x07\xf4%\xfe\xba\xfc\ -\xc7&\xc9\xea<\xb0\x93\xa3\x91\x95\xe0\xecrIs\xc1\ -y\xc4Ba\x05q\xce\xe4\xc3\x7f\xe0P\x86\xee3\xe5\ -9\x03<\xf2\xddjy\x9f\x7f_O\x96\x1b4\xc8'\ -G`\x93\xd7\xf3\x87\xff\xa6C6\x1c\xb1!\x1d\x8e\x84\ -\x83\x16V\x0d\x99\xa4\x1e\xdc\xdb\x06\xb9\xf6\xa4\x8ceI\ -\x932?\xeeh\x1a\x0d\x9d\xec\xd0k/\xd2P*\xa7\ -\xce\xee\xd3\x01\x7fFM\xaf\x9c\xa3Ok\xfa\x80\x10?\ -\xad\xe9\x01<\xdfG\xd3\x07\xf8\xf4iM\x0f\x1d\xc8o\ -ag\xe54\xfd\x83\x9d+\xe8\xd4\xbb\xf4\xda\x93\x9d\xc1\ -'a\xfbt\xf0\x8d\xf4\x02\xfb\xf0\x9c\xfc\xcaw\xd5o\ -\xbf\xfc\xcaE\xfb\xfc!2\xd9&\xeb\xbd\xc58\xad\xae\ -\x9c\xc2\x90\xe1:\xf9ir\xbf\xd9\xcc'\xcb\xb3\xc5\xfd\ -\xba\xe8\xb3\x1b\x0aNlv\x91|\x99\xfb\x98\x06\xc38\ -\xa5$\x03\xc7R\xd4F]\x87\x0d\x1d\xdat\xfc\xe9\x00\ -\x0e\x1c\xb7\xa6F\xf65nF\x1c\xe8H\xcal\xc7\xb0\ -\xa1?]\x1f\xf6t1\xbf\xfb\xdbd\xfb9m\x93\xff\ -\xea\x92}\x84\x9a\xd7\xd3\xd4\xa2\xda\xbc+\x00\x0c\x06\xf0\ -\xf4/\x83\x11\x13\x8aX\xc9\x87N\x10\x01\xce+\x95\x83\ -\xd3\x81\xa0D;\xca\xb9\x1b\x16\xdf0\x0c\x87\x7f#\xe6\ -\x0c\xe1\x9cK\xbdw\x83\x1d\xf8\xbf\x95h\xc9\xc3\xee\xf5\ -#\x04\x99y\xcb\x96\xc7\x0f\x9d\x8f\xa7\xd0\xba\x00\xdf\xf2\ -\xf8\x015\x9b\xd6D\x09\xce\xeb\xacr\x97b\x867\xf5\ -D)\x82\x80\xfd\xc5\xf1\xfdz\xf1\xdf? \xdfO\x16\ -\x85WA\xad|\xeb\x9fV\xe4u\xb3]\xaf~M\x8e\ -\x97\xabe\x92}\x1f\xf9E\x92\xe2\xd1\x08e\x088\xe5\ -\xd8\xafpT\x0b\xff\xbd\x9a/\xeb\xa5\xb7s \xfbb\ -\x0e\x7f\x8ee^6\x9bl>O\xd6\xeb\xc9c\xad\x0f\ -,M\x05\xf8\x98\x11\x0a\xf2\xe0\x9c\xb7\xd3\xfeam*\ -\x001\x9dL\xc9\xd4ow\x11\xb6}\xc4\x15\xa3\xc9z\ -\x9a2`\xceB\xef\xdf\xd5Q\xb0\x97\x04\xf9\x85\xad\xe3\ -\xcf\xeb\xe4\xfa\xe4\xe8\x87\x16K^r\xea.~#\xcc\ -\x5c\xd8,\xa1E\x82\x7fD+c\x99\x91\xf0\x9ds\xa2\ -4w\xd4\xe6Soe\x10d*\xad\x88\x13\x96\xd3\x9c\ -?\xae\xdb*^\xb7T\x5c\x03\xbf2\xc2R~E$\ -\xf6\x8b\xb9\xaa\xc3\xbf\x839\x11\xe7 t\x18t\x98\xa6\ -QZ\x9a\x06\x06G\xc6\x10\xca\xc0z\xb2\xff\x00TV\ -\xdc\xa9VT\xf2\x08T\x06'\x0f\xfe\x177J\x1a\xf5\ -\xd4\xe4\x9b\x15\x9f}\xf2\x1d\x12(\xd0\xf1\xf9C\x02\xdb\ -1W\x09*[1\xa7\xfb`\x1b\xc64\xd1\x9cS\xf7\ -\xd4\xe4\x9b\x15w&\x7f@\xe4\xb5?\xdb4\x16*\x9d\ -1\xfd\xb0\x0d\xd1V\x09\xd03\x9e}\xca_B\x09b\ -\xb5`\x1c\xa2\x0d\x06\xb3\xb4\xca\x8a\x9c\x8f|\xb4\xc3\x8c\ -&\x16]\x94J\xb4c8\x11\x8eK\x91c-\x8dv\ -\xa0\x22\x93\x92\xe6\xe3\xc5x\xc7\x81\xb7\x22\x00\x9f\xec\x19\ -\xf0\xe6\x97\xa9\xc2x\xb3\xfd\xa8\xed.\xbc\x01\x8fp\x8b\ -x3D\x0a\xc9\xeahS\x96\x18m\xabXcL\xa2\ -\xa0\x16>W\x8a5\x0b05\x155\xacq\xc287\ -N=\x0b\xb7\x15\x0bl\xadX\x13/\x8f\xdb@\x17i\ -I\x95\x93Mn\xabU\x84BF\x1d\xa1N)\xfe,\ -\xdc\xc6Y\x17\xde\xfa\x92Rc\xb9\x85`%\xc3[\xfe\ -Kh\x01\xae\xa8\x848\x01\xf0\x86\x81\x9ai0\x1b\xee\ -\xc1I\xa9*X\xd3\x96P\xad\x85\xabs[\xb3\x22b\ -\x0d\xd8\xd7R\xa9\xd3\xb8\xee\xa0pN\x1a\xe0\xdf\xde\xc2\ -P\xb4\x06T\xaa24\xd9\x8d\xe7\xa0CW\x8f\xe7z\ -\xa66n\x80\x84\xa8\x0d\x0fM\x9dgA\xcb\xe9\xc2V\ -zQ\xe7\x8aP\xa5\x85\xaa\xb3\xac$\xa0\x96\x0ag\x13\ -\x91\xaf\x0d\x11V\x99\xa2b7\xfb\xf4oy\xfdFX\ -\xd0\xf2Z\x93\x13\x01\xad*h4M\x9d(\xdc)4\ -\xaa\xc2\x11\xa1\x99\x13U_\xacY\xef\xba\xa5\xde\x1a\xf9\ -\x8eH\xcd\xa9\x89\xf1\x09\xc3\x92#\xc0a\xa1\x9a3\x95\ -I\x0e\xe3B+\x09\xee53 F\x9aY7\x1c\x81\ -\xfaU`\xec\x1d\x7f{8\xab\x0bkY\x7f\xac.\x95\ -\x95 \xe2]\xac\x0e\x1d\x8a:\xab\xf7\xcd\x01Vup\ -\x80\xe3\x15\x0e\xd0\x10\x10\x839\xe7\xb2\xc2\x01\x10\xbeX\ -`\x22&+\x1c\xd0\xacw\xddR\x0f9\x00#\x1f\xb0\ -\x88M\xe6\xdf\xa5.'\xd6P\xf0\xd5R\xea\x82\xf7!\ -\x1c\x84R\xde\xf6\x0ap:\x14\xc5\xaf\x94\x11%]a\ -\x86\x9f\x16\xa5\x03\x19\xc0\xf5\xb6tEAo8m\x8d\ -\xea\xa4\xbf\xd3=\xac\xb8\x81\xbf\xa6\xfa\x1b6c`\xcf\ -y\xe7\xb0\x1d7\xf5a\x8f1\xc7\xe1\xfd\xbbb]\x04\ -\xd30f\x98\xd6\xf2\xa6:\x82\xaf\xf3\xe5l\xf5u\x94\ -\xa7\xbf\x98f\xd6I\xf6<\xcb\x961\xd2\xd6\x1f\xdfM\ -n\x92\xcd\xe7\xc9\x0c\xf3a\xf8\xee\xa3l\xf9\x06\xa7\x90\ -\xba\x877\xf7\xf3Y\xb2]-\x12`9\xcc\x82a\xc5\ -\x835\xf4P-\x87\x8f\x7f\xb0\xfa\xf4o\xc0P[\x93\ -O\xab\xf5,Y\x17]\xb0Z\xb1_8\x06\xe1\xd3\xfe\ -\xe3\x1f\xe0\x80\xf2\xe2tA9\x1d0\xe0\xf2\xd3d\xd3\ -\xc8_\xf9m\xb5\x02q\xe0`\xbd\xd1T7f\x8d\xb2\ -)5\xe1\x8ae\xfe[\xf9$\xd3\xbdVim[1\ -\x09MM\xb3U\xf6\x08\xdb*\xd9\x00x\xbf^\x83P\ -\x8d\x16\x93\xc7d\xdd\xcca\xaa$-\xdd=\xf8\x922\ -\xab)+\xd8|^}E\xdc\x9e\x1cm\xd7\xf7\x8d9\ -b\xf9\xe8n5_\xa2\xbc\xee>\xceFu;y\x98\ -\xdf\xce\x7fKf\xb8\x80\xff\xfe]\xadu\xc9\xf5?\xc1\ -\xaf\xab\xf5\xea\xf6o\xeb\x04\x10\xf6!\xd9n\xe7\xcb\x9b\ -\x9c\xa1\xd3%\xb9\x87Gl\x92\x15\xad\xd6\xf3\x9b\xf9\x12\ -\xf3\xab\xb2\x81\xe6E\x8f\xd5\x22\xcc\x95\x028\x88\xeef\ -\xd9c\xb5,'+rMF\xd6\xc1 \xb9\xbdk-\ -\xaf0%/\xabVJeY\x9au\x06}\xa5\xc2\xb5\ -+M\xef\xdf\xbc\xbbM\xb6\x93\xd9d;\xc9\xb9)\xff\ --\x8e\xde\xbf\xf9\xd3;\xcc\x87\xbb\x86j\x7fJ\xbf.\ -\xe6\xd3d\x03\xb0\xaa\xbf?\xf8\xac\xb83\x5c?\xf5\x19\ -J8_?\x06\xe4\x88\x06\xa5i\x9d\xce,\xe3y\x90\ -\x90\xdb_\x92\xeb\xed_=\x123r\xfan\xde\x8d\xf3\ -\x11\xbc[\xcf\xae\x8f\xff\xf7\xe2\xea\xfd\xbb\xe9\xf4\xf8_\ -\xab\xf5\xaf\x99e\x80\xd2\xc9\xa7\xd5=\x80\xc7,\xa9\xe9\ -q\x9ax\xf5~~\x0b\x02\x83y\x80\xff\xf3p\xbb\x00\ -uR<\xc0:H\xd2\x5c\x1b!\x80u\x92\xa6\xf6\xb5\ -fC\xce\xa6\xb7sl0\xfe\xb0\x9d/\x16?#\x5c\ -\x8fN\x843\xdf.\x92\xf7\x1ez\xfe5\x1b\xdc\x8b\x19\ -e9\xa0q\x81\xc0qNc\xc0ji\x1f\x90\xf8\xa7\ -?\x7f\xfcp\xbd\x9aN\xb6\xf7\xeb\xc9\xc7\x94\x03v\x8d\ -@]\xbf\x8bB\x0f5vQ<\x01\x0b\x8d\xde\xd9\x13\ -\xdf\xab'\x1e\xe8\xc9E\xf4\xc4\xf6\xea\x89\x05zb\xbc\ -\xd9U\xbe\xa9\xd3\xb2\x80\x9f\x82\xf5{\x1a\xe5N\xc6n\ -p\xf0\xeaw20\x9e{[ \x0c\x11\x22M\xb6D\ -\xd6\xb5\x0f\xd4\xb1\x85\xd4\xb1\xf9\xd4\xb1mU\xdbS\x1b\ -\xb4n\x91\x0d\x9a[dOn\xaa\xb5\xb5\xd8\xe9!\xdd\ -U\xdb\xee8\xa2#\x08&\xa8\x15\x8e\xc9#D\x22\xe6\x91/S\x19\x8ee\xb2\xddM\ -\x89W\xcfd\xe5vY\xced\xdcj\xf9\x07\x93y&\ -\x83\xb0\x80)\x96\xee\x15U~\x02\x971\xe248\xd7\ -C\xce,\x11T*\xbd\xc3d\x1e\xc7\xd5\x9d\xea\xb75\ -\x96{\xb2\xbf\xd6\xeeF.\xeb.\xdbe\xf8\xdeX\xeb\ - \xe8>l\xd0\xc1<-<\x973\xa8RY\xfa\xe3\ -\xd3\xf2\xc9_\x97|\x1e\x05\x94Q\x95q\xbaY\xec\x0f\ -\xd6\xfa&\xd6\x02mh\xf7d\xad\xff\xa4L\x89'X\ -\xee`\xf3\xa8_\x17\x8e\xeaZ\xc6Q\xfd\x87\x19\xf4\xba\ -C9\xad\x9c\xe1\x99\xee\xc8\x7f\x8d8UDX\x89k\ -\xba\xd6\x11.\x0cG_\xab\xca8%.\x19-\xa7\xa7\ -!\x96v\x8aI3d\x94\x18'\x84\xb4\x83\xf3\x81\x10\ -\xe0\xae\x19\xdcK/K\x19\xf4\xc6\x94\xe5|(81\ -\x1c\x1e\x8bJ\x99r\xc4\x18\xae\xb1qY\xa89\xe1\x8e\ -\x1a\xa1\xb1L\x1b\xa5\xb4\x1aj\x89[8Rk\xfff\ -\xa0\xe4\xd2\x8a\xa16\x849\xab\x8c\xc3\xd6X\xaa\xa8\xf2\ -\xa5\x9c)%\x9d/\x13\xceZ\xe5\xcb\x84\xe4\x86\xd7[\ -s\xc5\xb8T\xd0\x9as\x22$\xa3P\xd1\x12\xeb\xac\x84\ -\x01\xc1\x5c\x04\xb5\xa0a\x87\x1a\xc6\xc8\xb5\x92f %\ -\xd0@\x09%+e\xe7\x03\xf8N9\xd4\xe5\x95R\xc7\ -\x884\xd6\x81z\xd6\x8ah\xcd\x04\x87YS\xf0f\x0d\ - h\xa8\xa0\x17\x8e~-\x0e\x9cZ\xd0\xde\x16\xfa\x01\ -\xfcp&\xac\xe0\x03\x8b\xac\xa0\xb9q9\x1e\x15o\xc5\ -\xf8o\x83\xaa\x10\x17\xe9\x930\x05\xce\xa0\xc6\x8f\xa1\xd0\ -\x0aw\xd9^\x99Xw\xfb]0eFA\xef\xdb,\ -\xa5#\xff\xc5\x04\x90\x86\x19\xc7\x86\x0aX\xcar!D\ -\xc52\xfa\x8d=\xae\x06 G\x12\x9c@\xa3@\xe0\x80\ -D~A\xb6\xfc\x92\x89\x9bt\xc4\xaf\xb0\x0f\xf7\xaa\xbd\ -\x0b\xba\xc5\x22\xe6\x8dvuM\xfb\x13\xbf\x04\x99\x03\xdc\ -\xd5k\xf5\xdd\xa2\x5cn1\x99\xb8\x9dK2\xfe\xf0\xc4\ -h\xe7\x856\x8b`\x8d{[\xe7\x0f\x10\x0am@J\ -\x99xVF\xe92\x83\xb8\x97\xb4\x97\x19,\xb5\x197\ -\x1d\x8eV\xeb\x96\xfb\x08\xd3Y\x94D\x97\x8bQ\x8d9\ -\x09\xa2\xe1gA\xdc\x8a\xf8\x1f\x82\x1b\x86\xc4?\xcf\xcb\ -\xb42X\x06|\xa8\x95N\x0b\x81\xb8X\xe4\x94\x01\x05\ -W)r\xf8'o\x89%\x8c)\x02\x1a\x95c%\x8e\ -\x12\x02Z\x8aqK\x01\xebM\xf1i\xc9\xf3\xc0\xca\xff\xf7\ -\x97_~\xbe\xf8\x080>\xa6\xeb~\x98n!A\xd9\ -H\x9d\xbd\x90\x86\xfe\x96R\x10\xa0(\x90X_\xb2>\ -9\x02?PC\xb4\x95e\x83=\x9dj\xd3\x95\xc7\xee\ -R//sZ\xd3_\x98\x98\x06a\x9d\x81HH&\ -#54\x04p\xe5\xa8\xc5\xb7\xd5\xde\xfc\xa9x\x9d\xaa\ -\xfe\xe6U\xfb{j\x8e\x9f\x17\x98\xf1o@\xa6\xefr\ -\xb7@aa(\xfa2\x0bkK(&\xdb\xe8\xa9\xa3\ -5CxK\xfeX\x15\xd9\xe2\xe3\x9ex\xc3\x840\x89\ -\xc9\xd3\x5ce\xc8\xf7\xf9\xb2\x06\xa22f\xb3\xf8\x1c\x93\ -\xc1$\xc4J\x92\xb1\x8c\x1e\x98\x09V\xd6\x89B\xd8\x85\ -\xff\xd4\xa7jX\x00a s!8W\xfe\xd3\x80#\ -\x82p\x1c\xc8p\x08\xd4\x99\xff4@\xa9\x0c\xfb\x8d\xd7\ -\xde\x9e\xc4\xbc\x8c\xc1\xbc\xcf)eR\x14\x88g\x8a\x13\ -gt\x96K\xf8\xe0\xb3\x1d9\x087/\xf1^\xaf\xd2\ -\xc5\xf7\x86\x1a6\x1ce\x7f\xeb\x7f\xc0\xb9\xb3\xa0\x5c\x86\ -x\x10\x0apZ$\xd3\xb7\xd1\xd0\xd2~hhyo\ -4\xb4\xf2@\x1a\xaa(\xe9\xc1\xc5\x13WH\x06J\x0f\ -\x98\x0f\x01\xca\xcc\x94\xd2S\xaf\x83y\x94\x9aP\x5c\xba\ -0q\x98o\x9d\xa6\x0bb\x8cK\xaeC\xa0.\xfd\xa7\ -\x0e\xca\x85\x05Q@h\x12\xc3\x0f.,\x8b\x9aeI\ -:{\xb2\x84;T\x16u\x0c\x1dG\x12|\x06)i\ -\x95\x90\xb8@\xc0\xf2\xb7J|N,PR\x0a\xa5(\ -+\xf5`\xad\xd2\x93\xf2\xd8.\x96L\x80\x15\xa6\x16\xac\ -:\xd8[+\xe4\xb7\x0b\xa4\xa1\xfd\x08\xa4\xa1\xbd\x09\xa4\ -\xa1\x87\x0a\xa4\x89R\xaa\xa0\xda\xa8\x14%\x199sD\ -qp\xecJ\xa5\x8a)f\x8a\xcbR\x1e\xebu\xba^\ -s\x1f\x01\xb5\xfc&\xa7\xd6C\xcet\x1f\x94\x0a\x0bp\ -\x14\xa5XXzc)\xc5\xc4\x81\x94\xb2Q\x22\xc7-\ -\xb8\xd7Y\x86\xad7\x7f\x94\xe3{K\xa5\xdb1\xc2%\ -\x0a\xc3mEs\xd6\xea\x1c*n\xe0\xce8)\xf9\x90\ -Kb\x98\xebC\xdc\x98\xed\x87\x88<,\xb6\xb1D\xe4\ -\xfc@\x22\xbao\xf4\x1e\x1d\xbe\xec\xc7\xbb\xbd\xc7\xa2\xce\ -7c>\xcd\xdb\xed\x01\xf3a1\x8c\xc5\xbc`\x87a\ -\xde\xd1(\xf1\xc1\x136\x1c\xf8\x82\xa5\xfc\xe0\xf2\xb9\xb5\ -\x15M7BU\xa7mE|jU\xf68\xcf\x839\ -;\xe4\x1c\x8f\xf2\xa0\xaa\x079\x11\xba\x1fj\x89\xb0\xbc\ -\xc5R+=\x11\xe7\x00j\xb1\x18j\x81e\x077A\ -\xd7\xdd\x8b\x86\x9c(\x88I9\x13u\xef\xa2/9\x09\ -\x86\xa5\x91\x98\x97ay\x8b\xc6\xbc;\x10\xf3<\x0a\xf3\ -\x9cXmY\xa7\x86R\x12\xecL\x96q\xde3\xde\x95\ -\xec\x07\xef*,9\xb1xO\xcfD9\x00\xefQ\xeb\ -\x0aJ\x11iD\xd5\xa1\xb6\xa0F\x04\xaf\xe1\x1d\xf7z\ -\x14\xaf\xe2\x9d[\xa6)\xfbv\xbc\xebp\x04\x12\x85w\ -\x1d\x96\x9bX\xbck\x13\xc2{\xf5\xf3\x04\x0d\xa2\x5c,\ -\x8c+\xb4,-\x84\xc6\xd5\x84\x8c\xcf\x11\xff\xfe]\x0b\ -W\xc8\x06\xaf\xd7\xf8^\xabj\xca\x7fj\xb8\xb32\xc4\ -\xfe\x1d\xabj\xc5K0%\x94C9?\xce'\x12\xc4\ -\x0a+\xb3\x98\xb0\x0d\xef\x12\xe2\x7f-duE\xe0\x85\ -\xe2]\x85\xc4(\x0a\xef\xea\xb0\x18^\xd0(\x8f\xa8\xc1\ -\xcdmxg\xa48\xbc\xe1%c]\x87\xc2\x80(\xac\ -\xeb\xc3\x22\x00p@bW\xc0\x0a\x9c\xb6\xe1\xbc.\x0d\ -/\x19\xeb!\xd7&\x0e\xeba\xaf\xa6k\x8bDP^\ -n\x91\x08K8c\xf9\xdb\xf0\xfe,\x14N\x94\xa5,\ -\xf3\x0d\xd7\xe9\x09\xd7\x06\xdf\xf3KK\xae\xd3V\x8c\x8b\ -\xa2$m%\x1d\xe3{\xed\xa3\x04\xd6\x10#l\xa55\ -a_Gj\x13\xb3\x80hMo\xee\x8e5\xb9\xd2\xdf\ -\xd9Jy\x82 \xa2$\x08\x84ZR\xe8\x1c\xb5\xfb\x12\ -\x04Z\x09i\xf3\xe08#\x88\xcaO\xcfxvr\xd8\ -\xb0\x0b\x14K\x0e\xdb\x9b\x17\x94\x1d\xc5}\x009d\x85\ -\x1c\x1cO!Q\x91\xf2!\xf1\xe4!\xe6\xbe\x9f|\xb8\ -\xf0bj,A\x5cX\xd4b\x09\xe2\xf4\x81\x04Q\x15\ -\x82H\x02\x01\x83\ -Sa/?\x96 \xaa\xb7m\x02\xa7\xdc\x81\x04\xa9\x04\ -\xd4J\xe3\xeb<.\x8e\x1c\x1a_D\xe2\xdfO>t\ -o\xe14\xa6\xb1\xf7E\x0e}`8\xcd*\xe1\xb4r\ -\x84\xb2\xec\xb4\xc9\xbd\xc9a\xf1D.\xdd\x90\x0e\xc6\xed\ -\xef%\x1d\x1d\xf9\x85\xb1\xe40\xbd\x85\xd3\xcet$\x8a\ -v\xdbn\x16\x95\xac\x88\xaf\xea\x818T\x16W}F\ -\xb5\xd3\x15\xdb-\x14QN\x96\x0b}\xf5\x1a/\xc9r\ -\xdb^\xa2\x05\x17\x8e\x16\xbaE\xa1\x129\x0bP\xfa,\ -K\xae\xf3\xb9\xd0\x8e8\xcd\xc3\xa2\xf0\xecl\xee\xfa3\ -\x02\xae?#\xe0\x82F\xa0\xcfO7\xd9D\xc5\xa00\ -\x8dg\xb7\xea\xca\x0a\x14\x94\x080\x16Y\xc9\x1a\x0f\x82\ -\x15N\x815\xb7{J\xd9\xf7\x92\x8f\x0b}u\xd9`\ -\x02\xa6i\xf4~\xdb\x19\xbf\xb2\xbb`\xc2\xc6\xa2S7\ -\x89\xa8\xadfO\x0c\xcd+q\x05\xc7\xecW\x9d/\x10\ -\xfa\xa4\xbfz\x1d,\xc1c\xa1\xa5\xea\x01Y,z\x93\ -\xcc\xba\xb3\xabs\xd9\x04\x13\xde%\xeb\xe6K\xd9\xc5\x97\ -\x5c\x13&\x94\xa8s\xe5~\xda\xe4{r\xe5\x95;?\ -\xbd\xb8l`(\x98\x1e\x17D\xf4\xa5\xb5gg\xbc\x09\ -\x86\x1f\xe6\xc2\x08\xd5\x85h\xa1\xab\x9b6\xaf\x08\xd5\x17\ -\xf6\xd2\x9e\xef\xe0(\xac\xbb\xb9\xc9\x8e2o\x83\xa5.\ -\xf5\xd9\x0e\xd9:R\x5cu\x10\xd4\xf9\xd9%;c\x0d\ -P\x22\xbcjk\xb5\x08\x1a\xba\xb3\xd3\x0b\xe3\xce\x9a\xb0\ -\xa2\xc5\xf6\x8c]p\xdb\x94~\x11\x5c\xb2}\x82\x9bt\ -\x177a\x8a\x00\xd7\xf9\xb6\xe9+\xe2\xa6\xab\xab\x8b\xab\ -\xb3&\x8ed8\xaf\x95K\xde\x91\x90sqq\xd6$\ -\x9b\x0c\xef\xa2(*\x82\xec\x04\xb0\xf4i\x933e8\ -\xc42\x86\x05\xdf\x9e\xb8\xba:?\xcb\xecz\x05V\xb4\ -\xc9\xbc\x02DY\xda\x04s`|%L\x17;)U\ -]V{E\xec\x04\xae\xbbQ\xa2\x81\xa3`V\x0a\xf5\ -\x11a\x10\x16SB5\xf1\xdd\xb1\xe8\xa1\xa5\x0b\xd2N\ -Ji\xb3\xcc\xaa\x0a\xac\x0e\x87\xd7\xa9\xa0v\xe2\x97B\ -\x8a\x1dX\xd1\x8b\x8b\xad`4;\xd0\x03\x8bZU\x87\ -\x80E\x12${\xe1\x81\xd5\xdda\xf4\xc0\xf0\xb8\x19<\ -)\x9a\x84\x05\ -\xfd\x7fk\xed\xa9\xad/\xdd2\xdb\x91\xb8#\xc2\xcb\x93\ -\xe2\xc2\x9c_5!\x1d\xf6\x96r;\xac\x0e727\ -\xa9mA.?W\xcdx\xd9\xd2\xb0\x1b\xa9\xb5\x09\x8e\ -\xeb\xf4\xfc\xf4\xea\xd45a\x85coC\xc3.\xa9;\ -w\x97\xae\x09\xab\xe3\x15S\xd3\x01\x8a:a/\x9a\xa0\ -\xc2\xa8\xb7,\xbbD\xae\x8d!N\xedyvyU\x05\ -Vt\xfaI+_\xb1\x03\xf3OT\x94\x1f\xb0+\x8b\ -\x06f,\xa5\xabh\xc3]\x89\xa4x\x16\x97\xa5;\xbb\ -\x00/E&y8&\x8f\x94I\x1e\x8e\xc8\xa3e\x92\ -\x87w=\xa3e\x92\x873\x0c\xa2eR\x84WC\xa2\ -e2\x98\xff\x13/\x93\x22\x8c\xfah\x99\x0c\xbe\xe8\x19\ -'\x93\xe2\xc0PIE\xb9\x87#\xed\x8f\x05\xe5\xe5\xab\ -\xe8#\xdc\xee4E.\xbd\x7f\x97\xb6^);\x0e\x82\ -\x81{\xfe\xb4L\xa2\xf41\x81g\xf51/\x92\x9cR\ -\x01\x9e\xcaPXb\x84\x96\xdc\x0dG\x06\x8flb\x8c\ -\xf3\x9e$Rv,\x91\xc4Id\xc7\x9b\xa3\xd1\x12\xd9\ -\xf1\xf6h\xb4D\xca\xb0e\x8b\x96H\xd5a\xd9b%\ -\xb2cq*V\x22;\xd6\xa6\xa2%2\xb86\x15'\ -\x91\xea\xc0\x98AE\xc5\x0c#\xa9\xaa\x09\xe4Y\xd0 \ -D\xb9x1\x92\x02\xd8\xcfTO\x15\xa8\xd4\x08\xcbb\ -E\x103\xfb\x08\xc2\xe8\xcfNS`\x0e\xb5\x18\xe2\xab\ -y\x80,\x15\xbbP\xe9\x0f\xd49o`\xab#\x85'\ -,\xcf\xad\x92\xd3\x91\xc1\x13)\xcf\xba\xe3\xac\x0a\xae\xc3\ -'\x06\x9d]\xd1\xcb&S\x99\xb0%\x0b\xae\xc2\x9e^\ -\x9c\xd1\xd3\xd3&\xa0\xe84\xd6V\xee4\xc1<\xd6=\ -\x82*\x13wz\x97 F\xaaJ*\xab&\xcc\x00B\ -J\x16\xad\xd5\xe0\xfe\x9eA\xe7\x8as0\xbe\xd7\x82z\ -\x0b\xde8Ua/\x82S\x16\x93?\x02\xb0\xc2<\xcf\ -\x99\x0b*\xad\xcb\x8b\xcb\xab\xcb\xb3&\xac0\xd7s\x19\ -\x1c\xd6\xc5\xe5%\xbb\xd8\x01\x15f{\xaeMxCS\ -_\xb8\x0b\xde\x80\x15|?\x93\xfaS\xf1\x22\xc4\x11`\ -\x85Qo\xb5\x0b\xc2jE}P\xdd\xc4H\x11\x80\x09\ -.M\xcb\xe3\xbf\xbbF\xed8\xc90\ -Z\xa3\xda\x1e5\xaa\xedO\xa3\x06\x93(\x0f\xd0\xa8\xae\ -G\x8d\xda\xf1\x9ae\xb4F\x8d_\xecm\xe5\x86\xf0b\ -\xef\x13\x02\x14y\xf6]C\x80\xf0\x0a\x16\xc7*A\xec\ -\x8e\x00\xe1y\xef\x8a7R\xa8_\x82Fe\x1d\xcb\xbc\ -\xb1\x1a\x95u,\xf4FjT\x16\x5c\xe7\x8d\xd2\xa8\x8c\ -\x1e\xaaQ\xa3\xb6\xe5\x81\xdc\xaez\x1c\x86$\xa0K\x5c\ -]\xa1\x02\x8b\x9b\xd2\xc2\x165\x22s~vE\x87\xb1\ ->\xdeL\x030\x07\xbe\x9af\xa2\xb6H\x1a\x98r\x8a\ -H\xc7y\xdd\x99\xafa\xaa\xac\xd1\x03\xa6\xfax\x8f\x8c\ -\xe3m\xb6!Lu\x1e+\x8f\x9fMv\xae\xfc|\x96\ -\xdf|\x90\xad\xa0\xcc\xf0\x10vc\x89eN\xe3)\xe1\ -\x12bl\xa3\x05\x9e'\x0e(\xa0\xda1\x8b'\xc4\x01\ -\xdb\x08E\x9d?c\x5c\x0ap\xb6\x87\x02\xcfr\x12J\ -\xf9\x22|\xf9\x86c\x90-\x9c\x90Vcc\xbc`\xc7\ -\x0a\xbcuH\x11\x0e:^\x88\x01|\x13\x12\x81\xe3\x15\ -Ax\xb4\x9d\xb6\x03\xc9\x08\x85\x90\x89\xba\xbc\xcc\xb7\x16\ -xy:\xba\x06x\x1d\x10s\xc0\xb2\xda\xdf/d\x84\ -\xbfL\xc8\xe2\x05\xec<\xbd\xdb\xc8Y\xad\x8cMo,\ -\x92\xdd\ -o\xb7;\xdc\xe3\x19\xa6\xef\x1b\x8c\x1c\xdeF\x883\xbd\ -\x9e\xdc\xce\x17\x8f\xc7\x17\xc9\xbf'\xff\xbc\x1f|H\xd6\ -\xf3\xebv.j\xbd\xca\xaf\xd6\xc9\xe2\xf8\x87k\xff\xd9\ -[~\xec\xb3\xcaO\xfb5}\x02\x8f7\xaa\xcdl\xf7\ -:\xab}.\xc1\x0a\xdf\x9c\xb5s\xdb\xd6\xec;\xdd\xff\ -\xd5zu\x99\xa3\x06tI~uY\xf6\xcb\x82+\xa9\ -A\x85\x83\x12\x05?\x983C\x1b\x17\xf3}\x83\xf4\xa4\ -\x9c\x91\xf8\xcf\xfe\x9a\x15\xa0G\x0bG\x85\x1bC\xac\xbd\ -\xab\xe3\x14Q\x5c\xe0\xd5%\xa5\x8e\xcb\x8alM\xc9\xe1\ -e\x03\xca\xd4\x94\x5c\xb3\xa8\xda\xb0\xd0r\x15\xf8\xcf\xa0\ -\xe5\x0a\xe8\x95{I\xb6\xc9\x03\xe8\x1c\xcd\x8f\xf6\xb1\x02\ -\xbd\x129R\xfc\xbf\x95\xc8\xbbz\xcdq\xd5\xa1\xd7\x0a\ -\xe2Kp+\xd0\x9d2\xd6y\x94\xfb{m\x9c\xc5\xa0\ -\x06\xc9\xa4\x88U\xda\x0a\x83\xa5\x16u%\xaf\x95\x1ap\ -\x91-\x83o\xe8\xcd0\x08\xcb\xd1)\x83Bc\xa4w\ -\xde4\x11\x0a\x023\x81e\x8a\x19\xa7\xc4\xc0\x80\x9bf\ -(xd&e\x01\xe4\xac\xbc\xa81\x92\xf2gF\xd2\ -6)v@|\xe4\x92,\x08\xcd~\x8d\xf0\x96\x1am\ -1\x18\x15N5\xae\x82\x17\xf9\xab\x94\x1d\x1c\xe1\xb9a\ -\xbc\xf9r\xf3\xfe\xff\x01 ;\x82Wk\xab\x00\x00\ -\x00\x00\x08\xc9\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\ -bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\ -\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\ -kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\ -\x12)\xc3\xf6 \xcd\x00\x00\x07\x94IDATX\xc3\ -\xcd\x98[lT\xd7\x15\x86\xbf\xbd\xcf\xdcl<\xe3\xcb\ -\x18\xf0\xd8\x13\xc6\xd8 0\xe1\xe2@0.\x09\x97(\ -\x14*Z\x12D\x12\x91Ji\xda\xb4M\xa5\xb4\x12/\ -U\xd4\x87>\xf4\xa5/\x09\xaa\x22\xb5\x12D\xa5j\x89\ -\xa1\xa4M\x80P\xb5\x10\xc5\xc4\xdc\x9b@\x8cKmZ\ -\x9b2C\x901\xe0\x16\xdb3x\xb0\xe7~\xce\xee\xc3\ -\x999\xb1\xa1x\xc6@D\x97d\xe9xk\xefu\xbe\ -\xf9\xf7Zk\xaf}\x84RJQ\x80\xa53\x19z\xfb\ -\xfa\xe9\xea\x09\xd1\x13\xbcL_\xff\x0d\xa2\xb7F\xd0u\ -\x03\x97\xcb\xc1To9\xb3\x02~\xe6\xcf\x9dE\xc3\xec\ -Z\xbc\xe5\xa5\x85\xb8\xcdk\x22\x1f`,\x9e\xe0\xcc\xdf\ -\xfe\xc1G\xc7O\xd3\xd9\x1dd(2L&\x93A)\ -@d'e=H)(.r1\xa3\xa6\x8a\x15\xcb\ -\x1aY\xbb\xb2\x99\xba@\xcd\x97\x03\xa8\x80\x8e\xce\x1eZ\ -\xf6\x1e\xa4\xfd\xef\xdd\xc4\x13\x09\xa4\x94\x08!\xc6\xcfS\ -j\xdc\x98\x02\x94a\x00\xe0\x9b>\x95\x8d\xebV\xf1\xfc\ -7\x9e\xa6\xa2\xcc\xf3\xe0\x00\x13\xc9$\xef\x1ehe\xf7\ -\xbeC\x0cE\xa2h\x9a\xb4`r@6\x9b\x0d\xbb\xdd\ -\x8e\x10\x02]\xd7I\xa7\xd3\xe8\xba\x9eU\xd2\x9co(\ -\x85\x14\x82%\x0b\xe6\xb2\xe5\xfb/2\x7fN\xfd\xfd\x03\ -\x8e\x8c\xc6\xf8\xd5\xef\xde\xe3\x83CG\xc9\xe8:B\x88\ -,\x18\x94\x96z\xa8\xaf\x9f\xc9\xecY\xf5T\xf9\xaa(\ -))A\x93\x92d2E8\x1c\xa6\xb7\xf7\x0a\x17\x83\ -!\xae]\xbbN:\x9d\xb6@u\xdd \xe0\xaf\xe2'\ -?\xfa6\xcb\x1f_x\xef\x80\x89D\x92\xb7~\xf3.\ -{\xff\xd2f)e\x18\x06n\xb7\x9b\xe6eKY\xfe\ -\xc4W\xa8\xa9\xf6\xe1p8\x10X\xa1g:\xca\xce\x8d\ -Fo\xd1\xdd\xdd\xc3\xd1c'\x08\x85.\x8d\xf3S=\ -}*?\xfb\xf1\xab45>:y@\xa5`\xe7{\ -\x7ff\xdb;{\xd1\xc7(W[\x1b\xe0\xb9M\xcf\xd2\ -\xd00\x17M\xd30\xb2\xf1uW\x87B \x84`x\ -8\xca\xe1\x8f\xdbhk;F\x22\x910C\xc10\x98\ -S7\x837~\xba\x85\xdaG|\x05\x01\xca\xdc\xc3\xd9\ -\xaenv\xed;4\x0en^\xc3\x5c~\xf0\xeawY\ -\xb4p>\x0e\x9b4\x7fE\x1eSJa\x18\x06\x1e\x8f\ -\x9b\x8d\xcf>\xc3\x0b/l\xa2\xb8\xb8\x18\xa5\x14\x9a\x94\ -\xfc\xeb\xf3+\xec\xd8\xf3\x01\xc9T\xaap\xc0\xd1x\x82\ -\x96\xf7\x0f\x12\x8eD\xad\xed\xa8\xad\x0d\xf0\xd2K\xdfd\ -\xe6#\xd5\xb8\x1d\x82R\x97\x8d\xd2\x22\x0d\x87&\x0ar\ -\xac\x94BJ\xc1\xeaU+xf\xc3z\xecv\xbb\xf9\ -B!9r\xea,'N\x9f+\x1c\xf0t\xc7y\xda\ -;\xbb\xd14\x89R\x0a\xb7\xdb\xcds\x9b6\xe2\xaf\xf1\ -\xe1\xb2\x81&\x05B\x80M\x0a\x8a\x1d\x1a\xa20Fk\ -\xcbW\xaf^\xc9\xd2\xa5K0\x0c\x03! \x9eH\xb2\ -\xef\xe0\x11Fc\xf1\xfc\x80\xe9t\x86\x8f\x8e}J\x22\ -\x91\xb4\x06\x9b\x9b\x9bhh\x98\x03\xca@\xdeF\xa3I\ -\xee\x18\xcb\xa7\xa4\xd3\xe9d\xdd\xda5x\xbd^SY\ -Mr\xfeB\x88\xce\xee`~\xc0\xcb}\xd7\xe9\xea\x09\ -!\xa5\xa9^\xa9\xc7\xc3\x13\xcb\x9b\xd14\x0d\xddP\xe8\ -\xb7\xc5]\xc60\xeb\xdbd\xcc0\x0c\xfc\xfe\x1a\x16/\ -^df50\x1aK\xf0\xc9\xd9\xae\xfc\x80\xe7{B\ -\x84o\x0e[\x89Q__Guu5\x86a`(\ -\x18M\xea\xa4u\x85\xa1\x14)]1\x9a\xd2\x0b\xc9\x95\ -;_$%\x8d\x8d\x8b(*r\x99\x03\x02\xce_\x08\ -12\x1a\x9bx]O\xf02\x99\x8cy\x02\x08!\x98\ -5\xbb\x1e\x87\xc3nMH\xe9\x8a\xe1D\x86\x9bq\x9d\ -h\x22CFW\x08\x01N\x9b\xc4i\x93\x05\xc7\xa3R\ -\x8a\x9aj\x1f^\xaf\x17\xc30C\xa7\xff?\x83\x0c\x86\ -\x87'\x06\xec\xeb\xbfa)b\xb3\xd9\xf0UU!\xee\ -p\x0e\xba\xa1\xacy\x0eM\xe2qix\x5c\x1aNM\ -R\x88)\xa5\x982e\x0a\x95\x95^K\x8c\xd1X\x9c\ -\xa1H\x1e\xc0\xe8\xad\x11\x10&\x84\xddn\xa7\xa4d\x0a\ -\xf7\xb0\x83\x05\x99\xa6i\xb8\xddnr\x87W&\xa3\xe7\ -\xddb\x9bn\x9d\x0c\xe6\x91\x94;?'\xb2\x94n\x10\ -M|\xf1<)\xc81\xfe\x15\x0a=\xcf\xc9d+r\ -:A\x81\x90fWRH\x85W\x0a\x92\x99\xc9\x81\x99\ -\xeb\xd4\xb8r&\xa5\xc4\xe5tL\xb8FVz\xcb\xac\ -\x7f\xd2\xe94\xe1p\xe4\x8e\x9e\xefAY*\x95\x22\x12\ -\x89X\x15\xc3\xe9pP\xe6)\x99\x18\xb0>\xe0GJ\ -\x13H\xd7uz{\xaf\xe4m\x08\xee\xc5\xa4\x94\x84#\ -\x11n\x0c\x0cZ\x80\x15e\x1e*+\xca'^\xb7\xa0\ -a\x16\xc5E.+1.^\x0c\x12\x8d\xde\xfaRT\ -\x0c^\x0c\x11\x8dF-\xc0\xba\x195T\x94\xb9'\x06\ -\x9c7{&3j\xaaP\x86\x81\x94\x92k\xd7\xae\xd3\ -\xdd\xdd\xf3@\x01\x85\x10\xc4b1>k\xef\xb0\xban\ -M\xd3hj\x9c\x87\xcdf\x9b\x18\xd0[^\xca\x8a\xa6\ -Fk \x9dNs\xe4\xe8q\x86\x87\x87\x1f\x18\xa4\x10\ -\x82\x8e\x8es\x04\x83\xe6\x91j(E\xd54/\xcdK\ -\x16\xe4]+\x01\xd6\xaej\xc67}\xaay\x87\x90\x92\ -K\x97>\xa7\xf5p[\xb67\xbc?8)%}}\ -W\xf9\xf0\xc3V2\x99\x8c9\xa8\x14k\x9el\x22\xe0\ -\xcf\xdf\xb4J\x80\xba@\x0d\x1b\xbf\xb6\xca\xeaR\x94R\ -\x1ci;\xc6\xf1\x13\xa7P\x8a{VRJ\xc9\xc0\xc0\ - \x7f\xf8\xe3\xfb\x5c\xef\xef\xb7z\xcd\x80\xdf\xc7\xa6\xf5\ -O\x15\xe6#\xf7\xf0\xfc\xd7\x9f\xe6\xf1\x85\x0d\xe8\xba\x81\ -\x10\x82D2\xc9\xfe\xfd\x07hm\xfd\x98T*UP\ -\x01\xcfY\xae\xe0\xf7\xf5]e\xe7\xce]tw_\xb0\ -\xd6\xbb\x9cN^\xd9\xbc\xa1 \xf5\xc6\x01V\x94y\xd8\ -\xf2\xbd\x17\x09\xf8}\xd9\xc6R\x10\x8b\xc5\xd9\xb7\xffO\ -\xbc\xd3\xf2{z{\xafX\xaa\xdcMQ)%RJ\ -\xe2\xf18'O\xfe\x95m\xdbw\xf0\xcf1\x09\xa7\x14\ -h\x9a\xc4f\xd3\x0a\xff\xb1\xb7_;?=\xdb\xc5\xcf\ -\x7f\xf9[\xae\xfd{\xc0:\x96\x0c\xc3\xc0[Q\xc1c\ -\x8b\x1by\xacq!55\xd5\x14\x17\x17[\x19\xa8\x94\ -\x22\x99J\x11\x09G\x08\x06C|\xd6\xdeA(\x18\x22\ -\x9d\xc9\x8c\x81Sc\xea_)\xaf\xbf\xf6-\xd6\xadn\ -\x9e< @{g7\xbfx{7\x17.\xf5\x22\xb3\ -\xb7\xb4\xdc\xa5\xdd\xe5r\xe1\xf5VPYY\x89\xc7]\ -\x82\xd44\x12\x89\x04\x91\xc8M\x06\x06\x06\x89F\xa3\xe8\ -\xba\xfe\xc5\xe5\xdd0p9\x9dh\x9a$\x16OXq\ -\xe8-/\xe5\xf5\xd7^\xce\x0by\xd7O\x1f\xbdW\xfb\ -\xd9\xb1\xe7\x00m\xa7\xda\x89\xc7\x93\xd9\xad\xc5ziN\ -\x95\xb1q\x97\xfb\x83l\xd7\xad\x14\x01\xbf\x8fW6o\ -\xc0n\xb7\xf1\xd6\xaf\xf70\x18\xbei\x96\x9a\x02!'\ -\xfcx\x94L\xa59y\xe6\x1c\xfb\x0e\x1e\xa1\xab'\xc8\ -h,\x01\x22{'\x11b\x5c\xdf\x98SX)\x85\xa6\ -iTM\xf3\xb2\xe6\xc9&6\xad\x7f\xcaJ\x88\xc3'\ -\xce\xf0\xc6\xb6\x16\x86&\x01\x99\xf7\xeb\x16\x98\xf7\x87\xae\ -\x9e \x9f\x9c\xed\xe4|\xcf%\xfao\x0c22\x1a'\ -\xa3\x9b_\xb9\xa4\x14\xb8\x1c\x0e\xca\xcb<\xd4\x05jh\ -j|\x94\xe6\xc5\x0b\x08\xf8\xab\xee\xf0\xd5z\xfc\x0co\ -n\xff\x1f\x90?|\x99u\xab\x9a\xef\x0dp\xac\x8d\xc4\ -\xe2\x0c\x85o2\x14\x19fd4\x86n\x18fWR\ -\xeafjE\x19\xe5\xa5\x9e\xbcYz\xf8\xc4\x19\xde\xdc\ -\xd6R\xd0vO\x1a\xf0AY\xa1\x90\xb6\xfbx\xc7}\ -\xd9WW.\x03\x18\x079\x14\x19f\xeb\xf6\x16\x00\x0b\ -\xf2\xa1\x01\xe6\x83\x14\xc2\xec\x11\x1e*\xe0X\xc8\xb1\xd9\ -=\x14\x19f\xeb\xdb\xbb\xcd\xd2\xf6\xb0b\xf0v\xbb=\ -&u\xc3\xa0y\xf1\xfc\x87\xaf`\xcerJn\xdd\xbe\ -\x8b\x1b\x83\x114M\xe2\xf7M\xff\xffQ0gm\xa7\ -\xda\xd9{\xb0\x0d\x7f\xd54\xbe\xb3y\x03\xff\x05\xc8C\ -\xdb\xc2\xa6D\x97\x9f\x00\x00\x00%tEXtda\ -te:create\x002023-0\ -3-08T17:27:26+00\ -:00\x9e\x98\xa5\x85\x00\x00\x00%tEXtd\ -ate:modify\x002023-\ -03-08T17:27:26+0\ -0:00\xef\xc5\x1d9\x00\x00\x00(tEXt\ -date:timestamp\x002\ -023-03-09T06:18:\ -41+00:00=\x00\x9f\x01\x00\x00\x00\x00\ -IEND\xaeB`\x82\ -\x00\x00\x08\x13\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\ -bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\ -\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\ -kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\ -\x12)\xc3\xf6 \xcd\x00\x00\x06\xdeIDATX\xc3\ -\xbd\x99[\x88\x5cI\x1d\x87\xbf\xaa:\xd3\xb7\xe9\x99\xe9\ -\xd9\xb9dHv\xc8\xc4\xc9\x5c7\x9b\xc4,AE\x05\ -/\xe8\x8a\x88\x0f\xee\x83\xe2\x93\xc8\x8a\x0f>\x88\xf8 \ ->\x89O>\xac\x18\x17E\x05aA\x03Y\x08^`\ -\x1f\xd4\x87 \x0b\xca**&lb2n4\x99\xec\ -e2\xd7\xccL\xf7\xe9>}\xce\xa9S\xe5C_\xe6\ -\xf4\xbd;\xa8\x05\xcd9]}N\xd5W\xbf\xff\xa5.\ --|\xdf\xb7\xf9|\x9e(\x8a\xf8\x7f\x15\xc7q\xc8\xe5\ -r(\xa5z?\x9b\xcf\xe7\xb9}\xfb6\xa9T\x0a)\ -\xe5\xff\x1c.\x8a\x22\xc20\xe4\xc2\x85\x0bd2\x99\xde\ -\x80Q\x14\x91J\xa5X]]\xc5\x18\x0b\x02D\xedW\ -Q\xb9\xeb\xf8\xbdS]\x87\x22\x84 \xd4\x9a[7o\ -b\x8c\xe9Om\x00)%\xc6Z\x820\xa8t$\x04\ -\xa2\xd6\xb1\x10\x88\xea\x95\xea}\xfc\x8a\xb5\x08\xc0\xc6`\ -;\x01\xdbj_\x22\xd6~_\x80\xf1\x11\xb6\x03\xb4\xd6\ -R.\x97\xb1\xc6\x1c\xc15\x83\xf6\xa1\xa2\x10\x02c\x0c\ -\xa5R\x09\xd7u+\xd0\xd6\xb6}VJI2\x99<\ -\x02\xac\xab\xd4\x06\xd4/\x97\xf9\xc7\xad[\x08!\x1a\xfd\ -4\x0e\xdbJ\xd3Z\x05\x18k\xd9\xd9\xd9amm\x8d\ -D\x22\xd1\xf6Uk-\xc6\x18VVVb\x0a6A\ -\xc5\xef\x8d\xb5\x08!8\x7f\xfe\xcd\x83\xdd\ -=\xd6\xb77\x01Q\xedKt\x05\x14\xad4\xfd)X\ -O-\xcdp\x80\x92\x8aBX\xe0g\x7f}\x89\xd7\xde\ -|\x95g\xe6\xde\xc5\x07\x17\xcf0\x92R\x14\xfd\x22\x91\ -\xb1T^mc\xda\x01\x01\xeb~Z\xado\x1f$q\ -\xe5\xa4\xe4\xde\xc1=~\xb9~\x15\x7f\xe8\x11\xcf\x9eY\ -\xe1\xd4\xd4\x14\xe5`\x1f?\xccS*\xbb\x95\x19\x081\ -8`\x97(oo\xe2\x18\x9c\x94\x920\x0a\xb9v\xe7\ -\x1aWo\x5caz\xdc\xf2\xd1S\xf3dSP\xf0\xb6\ -\xb1\xb6\xe2s\xe5\xa0\x88\xd6\xa61\x8fv\x00l\x9e=\ -\xfa\x99M\x1aM\x5c\xcdOJ*6\xf3[\x5c\xfe\xf3\ -e\xfe\xfe\xce\xef9wj\x8c\xf9\xe91\x84<\xa0\x18\ -\x00X*\x83T\x84\xda\x10\xfa\xba\x92\x1b\x07\x04\xecV\ -:\x9aX\x0a\xc9\xcd\xb7o\xf1\xf3?\xbd\xc4~p\x83\ -\xf7,d\x19\x1f\x09\xf0\xcd\x0e\x0aA%\x85I\x8c\xb1\ -\x18#(\x94=\x02?}\xd4h-\x09\xc7\x81\x1fC\ -\xb9\xb6&\xae\xf9\xe0\xda\xc6\x1b\xec\x1c\xbe\xc5\xd4\xd4\x0e\ -n\xf86\xa1\xabH'\x15\x89!\x89\x90\x16c4\xa1\ -\xd6\x94\xfc\x12[yA\xe8_\xac\x0eRT\xd3M+\ -\xd8 P]Ml\xb1|\xea\xdd\x9f$\x8a\x22.\xff\ -\xf1GLN\xaf\xb1<\xef\x13I\x88\x94`\xc8\x01\x05\ -XeIIH\x0d\x0d\xa1\x83\x08\xec``\xfd\x02\xd7\ -\xe7\x9cx\x0e\xcc$\xd3|\xfe\x03\x9f\xe5\xdb\xcf]B\ -\x1f|\x8c\xbf\xfcm\x04\xab\x15\xe3\xa3\x82\xdc\xa8d|\ -T26\xaa\x18\xc9\x0aR\x09\x8b\x8e\xf9`7\xb8\xf8\ -\xcc\xd4\xab\xd4\x9eiYB\xc7_}\xdf\xd2E\xbe\xff\ -\xc5K\xacN<\xcf\xaf^\x99\xe0\xdf\xff\x92\x8c\x0d\xc3\ -xN0:,\xc8\xa4\x04\x09\xc7\x10\x85\x15\x05k/\ -\xb7\x03\xed\xa6d\xf3\xa7\xad\x82\xedFh\xac\xe1\xf8\xc4\ -\x0c\xdf|\xeek<{\xfay~\xfb\x9bE~\xf1k\ -E\xe4C:\x0dJ\x09\x94\xb0DaT\xe5\xeb\x0d\xd6\ -\x0d\xa6\xab\x89\xbb-}\x86\x9c!>q\xf6\xc3\x5c\xfa\ -\xd2\x0fx\xf4\xd6\xc7\xf9\xcew\x87\xb8s\xd3\x10i\x83\ -\x8d,&4T\x12c\xab\x89\xfaQ\xb1\xdbs->\ -\xd8)5Xkyf\xf1\x1c?\xfe\xea%\xde?\xff\ -\x15~\xf2\xd3'x\xf5\x9a\xc5\x04\x06\xa3\x8f\x14\xec\x04\ -\xd7\xcb\xac\x9d\xfc\xb71\xcd\xf4(\xc6\x18\xa6r\x93|\ -\xeb\x0b\xdf\xe0\xdc\xe93\xbcp\xe5\x05\x90\xf7\x88BS\ -\x9f\x9ez\x05\xc9\xa0\xc5i\xd7@\xbbNj\xd7\x8a\xc9\ -\x1d>\xf7\x91\xcf\xb0zr\x81\xef\xbd\xfc\x22\xf9b\x99\ -\xe1T\x86\xa3\xddS\x7f*v3s[\xc0n\x0f\xc6\ -*+jZ\xc3\xd9\xd3O\xf3\xc3\xaf\xbfH9(\xf3\ -\xc4Hn`\x15\xad\xb5-\x9b\xa6\xda&\x8d\xc711\ -4\xa6!k-\xd9\xcc0\xd9\xccp_\x03\xacg\x07\ -c\xeap\xb5\xfdqm\x9f\x22\x04\x18{\xb4\xa2q\x9a\ -_\x1e$\x89\xc6\x1b\xee\xa6z\xdc=\x8c1\x18c\x88\ -\xa2\xa8\xfe\xa9\x01\xd7w\x8dR\x11\x99&\xc0n\x10\x0d\ -\xdb\xd1>}\xa7]\x160\xc6\xa0\xb5FkM\x10\x04\ -\xf8\xbe\x8f\xef\x07h\x1db\x8cA\x08I\x221\x84\x91\ -\x0eA\x10\xe08\xce\xe0&\xeeG\xf1f\xd08\x5c\x10\ -\x04x\x9e\x87\xeb\xba\xb8n\x11\xcf\xf3\xf0}\x1f\x8b%\ -\x93\xce \x1c\x07\xd7-\xf2\xe4\xf1\x19r\xb9\xdc\xe3\x03\ -v\x83j\xae\xab\x994\x08\x02\x5c\xb7\xc8\xfe\xfe>{\ -{{\x14\x0a\x05\x820$\x9dN359\x81\xb1\x86\ -G{{\xcc\x9f\x9acnn\x0e\xa5\xd4\x7f\x0f\xb0\x97\ -i\xc30\xc4\xf3<\x0e\x0f\x0f\xd8\xdc\xda$\xd2\x11\xc7\ -\x8e\x1dc|\x1b\x1b\x1b,-,2\ -;;\xdbp\xbc\xd2\xba'\xe9\xb1\x1a\x1e\x04\xb6\x96J\ -\xb4\xd6\xf8\xbeO\xb1X$_(\xa0\xb5ftt\x94\ -R\xc9c\xbd\x06\xb7\xb8\xc4\xec\xeclK\x9b\x0eT\x0e\ -\x15\xcb\xe5r\xdb\x13O!\x04\x9e\xe7\x11\x86\xe1c\x81\ -\xd6L\x5c\x0b\x8eR\xa9\xc4H6K\x22\x99`\xfd\xc1\ -}\x1e>|\xc8\xf2\xf2\x0a'N\x9ch\xdb\x8e\x93H\ -$PJq\xf7\xee\xdd\x8e\x8e^,\x16\xd9\xde\xde\xee\ -xT\xd6KAc\x0c\xa1\xd6\x84a\x88\x89\x22ff\ -\x8e\xb1\xf9\xf0![[\xdb\xac\xac\xacr\xfc\xf8\xf1\x8e\ -m8ccc\x9c={\xb6c\xe7B\x08\x5c\xd7m\ -I\xc8\xcd \xbdT\xb4\xc6\x12\x19\xc3\xe8\xd8\x18\x16\xcb\ -\xee\xee.+\xcb+\xcc\xcc\xcct\x1d\xa4#\xa5\xecx\ -NW+\xc9d\x12\xc7q\xba.\x22\xba%v!$\ -R\x0a\x92\x99aT\x22\x89\xe7y,--3==\ -\xdd\xd3\x0a}G\xb11\x06\xdf\xf7\xeb\x8a4C5\xab\ -\x0e\x95\xc3J\xad#|\xad\x89T\x02\xab\x86\x10\x81\xcf\ -\xd2\xd2\x12\x13\x13\x93}\xf5\xdb\x17\xa0\x10\x02\xad5\xd7\ -\xaf_\x1f\xe8\x9f\x80\x8a\x0fBd+\xe6M%\x93<\ -\xb5\xb2\xdc7\x1c\xc0\x7f\x00\xb3\xfd\x1fpe\x8eW\xd4\ -\x00\x00\x00%tEXtdate:cre\ -ate\x002023-03-08T1\ -7:27:26+00:00\x9e\x98\xa5\ -\x85\x00\x00\x00%tEXtdate:mo\ -dify\x002023-03-08T\ -17:27:26+00:00\xef\xc5\ -\x1d9\x00\x00\x00(tEXtdate:t\ -imestamp\x002023-03\ --09T06:18:41+00:\ -00=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB\ -`\x82\ -\x00\x00\x08\x01\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\ -bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\ -\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\ -kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\ -\x12)\xc3\xf6 \xcd\x00\x00\x06\xccIDATX\xc3\ -\xcd\xd8\xdbO\x1b\xd9\x1d\xc0\xf1\xef93\xe31\x17\x13\ -c\x1bc|\xc1\x10\x08\xe4\x02A u\xd3\xa4/]\ -u\xb7/\x9b\xedk\xab\xb6\x7f]U\xf5\xa9j\x1f\xda\ -\x97UUU\xda$\x0d!\x81\x84\x84\x90&\xdc\xcd\xc5\ -\x06\xdb\xc4\xd8\xd8xf@\x9a!\xf6-\x85\xd5\x80V\x09\xba\ -\xd1\xec\xb5\xddb\x81o\x1fL2\xfd\xe2\x0d\xfe\xb6 \ -\xa9\xfe!\xaa\xa5m\xb4\xc3\x06\x80@\x13\x12\xa5@h\ -\x06\x8e\xf0\xb3\x9a\xd9 \xd6\x1d=\x1f`\xb5V\xe5\xc0\ -r\xf1\xb5\xf8\x11Rby\xd0@P\xde\xdf\xe7\xdb\x99\ -\xe7L>yN\x03\x83d\xffu\xae\x04\x83\x14\xf3y\ -\xaa{\xdb\xcd\xb9#AH\x81\x8b\xe2\xa0\xa1p\x94@\ -\x1a-,\xaf\xcesch\x80x<\xf1\xf1@\xc7v\ -h\xb8\x1e\xba\xae\xa3k\x12C\x17\x18\xba \xb3\xbe\xc5\ -\xcb\xf9%:\xbb{\xe9\x0cE0\x0c\x1d\x81@\xf1\xe1\ -\x8e%$x\x02l\x0f\x1a\x1e\x08!\xd9\xd9\xd9\xa1n\ -\xd5O\x85;\x11(\xa5\xc0sm@!\x04hR\xa0\ -ip{t\x98k\x03iV2yV3yj\x07\ -\xf5\xe6\xfa\x01P\xcd\xa7\xe0p\xd7\xd1\x04\xeap/W\ -^\x03\xc7\xb60}\xa7?\xfa\x8e\x05\x06\x02\x01Z}\ -\x82J\xc3B)\x0e\x17\xb1@\x0aA0\xd0\xca\xc4\xad\ -4}\x89\x08\xef\x96\xb3ll\x15QJ\x1d\xc2\x04R\ -\x80\xa7\x04\x9aPhB\x80RX\xb5\x12\x9a\xf0\xe8\xec\ -\xec<\x1f`(\x14&\x19\x0b\xb3Q\xb5h3\xc0\x90\ -\xcd\x8a8\xec))\xa0\xbb\xb3\x8d\xf0\xf8\x00\xb9T\x17\ -\x93\xd2\xa1\x5c\xd8@\xa0\x0e{\xf1h\xc8\x15\x07u\x9b\ -\xbd\xfc\x1a\x91p\xe7\x99N\x93c\x81\xed\xed\x01R\xc9\ -\x04F\xc9\x01\xcfFy&NC~\xd7\x9bGP\x9f\ -\x14\xa4\xa3W\x88|y\x87\x1b\x83=\xf8M?G6\ -\xe5\x81m+\x8a;\xdb\xec\xe5\x16\xf8\xe9W??S\ -vs\xecF\xad\xeb:\xe1p\x04\x1f5\xb2[\xebl\ -\x15\xeaTj\x1evC}\xb0 \x84j\x16\x14\xf0\x19\ -\x8c\x0d\x0f\xd0\xe2\xf75\xcfd\xc0v\x14\xc5\xf75V\ -\xdfL\x12\xea\xf0\xf3\xd9\x8f\xee\x9c\x1aw\x22\x10 \x12\ -\x89`h\x82\x9d\x95\x19V\x96\x16\xc9\xbf\xb7\xd9\xdbw\ -q\x5c\xf0\x0e\x99G\xd4\xef:\xf5\xf0\x0f\xcbVl\xef\ -Z\xbc{=C~\xed\x05\xf7\xef\x7f}\xe6\xb4\xeb\xc4\ -\x84UJ\xc9\xc8\xc8m\xde-,03\xfd\x17\x5c\xc7\ -Bp\x1b![\x09\x074\xfc\xdf+A\x00\x1e\xe0\x02\ -\xfb\x96b=Wcvz\x8a\xf9\xc9\xbf\xf2\xb3\xcf?\ -\xe7\xceg?>\x13\xae9:\xa7LX\xcb\xe52\x7f\ -\xfa\xf3\x1fy2=KW\xdf\x047'\xeeq\xb5/\ -A4\xe4\xa7\xcd/1\xe4\x7f\x86\xf4}\xb9\xceZf\ -\x8b\xb9g\x8fXz=\xc9\x97_|\xc1\xd7\xf7\x7f\x81\ -\xae\x9f\xfd\x86qj 4\x93\x87\x87\x0f\x1f\xf0\xcd\xdf\ -\xbea\xb7\x5c\xa7+9D\x22=H$\xdaM\xab\xdf\ -\xc4\xb1-J\xbbyr\xebK\xecl.\xd1bj\xa4\ -\xd3}\xdc\xbb\xfb\x13FGF\x11G\xa9\xd9E\x01\x8f\ -\xa2P(\xf0\xfc\xc5\x0c/_\xce\xb2\xbd\xbd\xc3\x81e\ -\x01\xcdE\xd5\xd6\xdaB2\x91`t\xe46J\x81\xe9\ -7\xc9\xe5\xb2\xd8\xb6\xcd\xc8\xad\x11\x86\x87\xaf_<\xf0\ -(\x5c\xd7\xa5R\xa9P\xab\xd5p]\x17\xc3\xd0ik\ -k\xa7\xbd\xbd\x1d!\x04+\xab+hR\xa3\xb8[\xe4\ -\xd9\xb3)\x84\x90\xfc\xe6\xd7\xbf\xa5\xa5\xa5\xe5\xd4u\x9c\ -\xb8\x8a\x8f\x0bM\xd3\x08\x06\x83\xc4\xe3qR\xa9\x14\xb1\ -X\x0f\x81@\xe0\xf0^\xd2\xe0\xed\xdb\xb7\xd4j5R\ -\xc9$\xe9t\x1f\xa5\xd2{\x9e?\x9f9S\x1d\x1f\x05\ -<.t]'\x95L177\x87\x94\x1a\xa9T/\ -==q\x9eN?\xa5X,^>\x10`xx\x18\ -\xd34y\xb7\xb0@w\xb4\x9bT*\x05J\xf1x\xf2\ -\x9f\x9cvf](P\xd7u\xc6\xc7\xc7YY^\xa6\ -vP#\x91H\x92L\xa6x\xf3\xaf7\xac\xae\xad^\ ->\x10 \x99L\x12\x8b\xc5\x98\x9f\x9f'x%H2\ -\x99$\xd0\x1e\xe0\xe1\xa3\x07\xa7\xba\xe1]8\x10`|\ -|\x9cb\xb1H\xa1P\xa0\xa7\xa7\xb9\xa0\xb2\xd9,s\ -\xaf_}\x1a\xc0`0\xc8\xf0\xd00\xf3o\xe61M\ -\x93x\x0d\xa0\xdf\xefgll\x8c\xa5\xc5\ -%\x5c\xcf#\x16\x8b\x91H$\x98\x9d}A6\xbbu\ -\xf9@\x80\xfe\xfe~\x82\xc1 \x8b\x8b\x8b\x04\x83\x9d\xc4\ -\xe3qL\xd3\xcf\xe4\x93I\x94\xf2.\x1f(\xa5db\ -b\x82\x5c6G\xa5R!\xda\x15%\x1e\x8fS,\x16\ -888\xb8| @4\x1aepp\x90\xcd\xcdM\ -\x0c\x9f\x8f\xd6\xd6Vz{\xd3\xf8\xfe\xc7M\xef\xa3\x92\ -\x85\x1f\x1a\xb6mS(\x16\xf0<\x8fJ\xa5B\x22\x9e\ -\xa0\xa3\xe3\xbf\x7fi\xb8\x14\xe0Qx\x9ewb\x8ex\ -\xa9\xc0\xd3\xc4\xbf\x01\x1d\xed\xef\xf6__\x8c\xd9\x00\x00\ -\x00%tEXtdate:creat\ -e\x002023-03-08T17:\ -27:27+00:008\xef\xae1\x00\ -\x00\x00%tEXtdate:modi\ -fy\x002023-03-08T17\ -:27:27+00:00I\xb2\x16\x8d\ -\x00\x00\x00(tEXtdate:tim\ -estamp\x002023-03-0\ -9T06:18:41+00:00\ -=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB`\x82\ -\ -\x00\x00\x07&\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\ -bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\ -\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\ -kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\ -\x12)\xc3\xf6 \xcd\x00\x00\x05\xf1IDATX\xc3\ -\xd5\x98\xddo\x1cW\x19\xc6\x7f\xe7\x9cY\xc7\x01\x7f%\ -n\xb1[\xd26n%\xabNh\x8a\x89\xd44\xadI\ -\xd5F\xc8\x154\x84P `\xa9A\xa8\xa2\x22\x01A\ -*@\x88\x1b\x90(\xfd\x03\xf8\x10\x14\xa9B\x0a\x5c\xc1\ -E\xa8@\x5cp\x11\xa9i\x9b\x08Jp\xd5$u\x08\ -\xa4\x0eI[\x9c8\xb1\x1d\x7f\xee\xce\x9cs\xde\x97\x8b\ -\x19\xaf\x9b^t7\xb1\xe3\x84\x95^\xcd\xce\xec\xae\xce\ -O\xcf\xf3>\xef\x99\x1d\xa3\xaa\xca\x0d\xfc\xb2\xd7\x1b\xe0\ -\xff\x1e0\xa9\xf5\x85r\x1a8{~\x9a\xd1\x89\x0aQ\ -\x94\xcd\xeb;X\xd1\xe0\x96\x0d\xb0\xa6\x82\xfb\x0f\x9e\xe2\ -\xafC\xe70\xce\xf0\xf3\xfdG\xf9\xd1\xbe#\xfc\xf7\xe2\ -\xec\x8d\x01\x18E9\xf5\xd68\xad\xab\x9bh\xeaX\xcd\ -\x13;\xee\xe5\xec\xe8\x0cO\xff\xec\x15^~}\x84\xe5\ -HWM\x05\x9d\x01509\x97\xb2\xea\xa6f\xbe\xf2\ -\xf9^\xee\xbe\xab\x9dg\x7fs\x84\xe7^8\xce\xcc\x9c\ -\xbf\xa6\x805{PU\xa8\xa4\x9e4\xcd\x10Q\x8c\x81\ --\xf7wq\xfb\xad-\xfc\xf9\xc57y\xe3\xf48\xdf\ -\xfc\xdc\x06\xbaok\xbb\x1e\x0a*\xaaBZ\x00fi\ -FZ\xc9\x98-\xa7tv43\xb0m\x1dX\xf8\xde\ -/\x0f\xf3\xa7C\xff!DY~\x05E\x22\x954#\ -K=\xaazY\xdf\x19\x0b[\x1f\xbc\x83\x93o\x8e\xf1\ -\xab?\x1e\xe7\xd8\xf0\x18Om[\xcfMm\x8d\xcb\xa5\ - H\x14\xcaiF\x96\x05\xb2,\xe03\x8f\xcf\x90\xf9H\x96E|\x16\xf2kY\xa0\x5c\xf1\ -$%\xcb\xd6\xfb\xd7\xd0\xbe\xaa\x81g\xf6\xfd\x9d\xdf\xfe\ -\xe5$\xe54,\x1a\xb0\xa6\xc5Q\x84J\x9a\xc3\xbc{\ -\xdb\xce\xdf\xea\xbb\x8eyuw\xb5\xd2\xde\xd6\xc0\x1f^\ -\xfa7o\x0c\x8f\xb1{\xc7=\xdc\xd1\xd9|\xed\x14T\ -\x89d\x99\xc7\x87\x88\x0f\x92\x97\x8f\x84\x10\xf1>\xe2C\ -\xccU\xf5\xf9y\xb9\x92\xf1\xc1\x95\x96\xbe\xde\x9b\x19\x9d\ -\x9c\xe6\xfb\xcf\x1d\xe2\xc0?\xdeF\xae\xf2\x9e\xa4\xf6\x98\ -\x11\xc1\x87H\x08Bu\x8d\xf9\xb0\xe8\x82r\xf3%\x22\ -\xa8\x0a*B\xcf\xda&\xce],\xf3\xd3\xdf\x0f2t\ -z\x8c]\x8f\xf6\xd0\xda\xd4\xb0\xc4\x80*\xc4\xa8\x84\xa0\ -\xef\xb1X\x17\xac\x95\xcb\xe1D\x16\xaa\xb5\xc9\xf2\x91;\ -Wr\xf0\xb5\xd3\x9c<;\xc1\xee\x1d\x1bX\xdf\xb5z\ -\x09-.\x16\x8c\xa2\xc4\x98W\x88\x0b\xd0!\xe4\xe7>\ -H\xf5\xe8\x0b\xbb\xbd\x0fTRO\x14\xcf]\xb7\x96\x98\ -\x9a\x9e\xe0\x87\xcf\x1f\xe6\x85\x97\x86\xf1\xa1\xbe\x99Y\xfb\ -vK\x05Q\x88\x11\xa2\x14\x15y\x0f\xb0\x12\xa3\x10\xe6\ -{4H\xb57sP!\x84@G\x9b\xa1%\x99\xe6\ -\xc7\xcf\x1f\xe0\xd9}\xafr~|n\xf1\x16\xe7\x8dg\ -\x10\x01\xa9\xf6`~Y\x15D\x15\x15\x10\xd1\xa2\x04\x89\ -\x82\x8a\x22Q\x08>P\x9e\xab011\xc5\xe8\x85q\ -F/N0=S\xe1\xd7\xbf\x1b\xe1\xc4\xf09\x9e\x1e\ -\xd8\xc4\xa6u\xb7`\xcc\xd5\x02\x22\xa0 \x02Z\x1d+\ -\x05`\x01-\x85\x921\xe4\x89\xafTRfg+L\ -\xcf\xcc255\xc3\xf4\xcc\x1c\xe5r\x99,M\xf1>\ -\x80\x06\xb2\xc9\xb7y\xf1\xe0%F.L\xf1\x93\xef\xf4\ -\xd3\xdb\xddqu\x80\xaa\xb9u\xe54\x16\x8d\x9f\xab\x94\ -\xdb\x9b[\x98\xf9\x90\x83\xa5\x19i\x9a\x92VR\xb2,\ -\xc3\xfb\x8c\xe0\x03\xaa`\xad\xc59K\x0c\x060\x18c\ -p\x16\xd0\xc0\xfbM\xa0\xba\xc6\xccT9\x03WA\xe6\ -\xd3Z$6\x07\x15b\x8c\xc5\x11\xc0b\x9d\xc39\x87\ -\x88C\xa2\xc3X\x8b5\x16S\xf8\x98$%\x1a\x9b\xda\ -\xd8x\xef:\xbe5\xd0Go\xf7\x87\x16\xd1\x83(\xd6\ -9\xacu\x18\xa3(\x8a\x15E\xad!J\xae\x841\x14\ -=\xa4\x88\x0a6\xba\xfc7\xd1\x16\x9f\x1b0\x8014\ -46\xf2\x81\xa6f>\xfdp/_}|\x13\x9d\xed\ -\xef\xbf\xcb$5\xd8P\x94\xa4\xe4(\x95\x92|\xde\xb1\ -\x10\x06c\x04\x88\xd5\x9e\xb4V\xb1V\xb0\xd6b\x8d\xa9\ -\xc2\x19\x0c\xce9\x1a\x1aW\xd0ys\x1bO=\xde\xc7\ -\xa7\xb6\xac\xa7\x94\xd4\xfeoSW\x8a]\xc9\x91$\x09\ -*\x92[l\x15\x11SX\x9a\xc7Y\xad\x22\xd6\xe6p\ -\xd6b\xcc\x82z\xaedQ\xe0\xbe\x9e\xb5|c\xe0a\ -z\xee\xbc\xa5\xb6q\xf5[,\xb8$\xa1\x94$\xc5N\ -Q\x84\xa4\xe8\xa7\xcb\x94\xab\xda\x99[j\xac\x05'\x94\ -\xf0|\xe9\x13\x1byb\xfb\x83\xb44\xad\xac\x1b\xae~\ -\x05\x93\x5cA\x91X\x00\x9a\xdcYU\xd4)\x22\x16c\ --\xc6.\xd8j\x13Cev\x8a\x8e&\xc3\x9e\x9d\x9f\ -\xe4\xa1\xfbz\xaa!YR@E\xb0\x89\xa5Tr\xc4\ -h\xd0B=UP\xa7X\x11\xac5\xcc/m\xad!\ -\x9a\x8cK\xe3o\xf1\xf1\xf5k\xd8=\xd0\xcf\x9a\xce\xf6\ -+\x06\xab\x1fP\x15S(hL\xac\xaa\x97+Y(\ -W\xcc5\x93X&&G\x89\x93#\xec\xf9\xcc\x03|\ -\xb6\x7f3\x0d\x0d\xa5\xab\x86\xab\x030O\xadq\x05 \ -T\xd5\xb3VsK\x01\xac!\xb3\x9e3g\x86\xe8j\ -\xb6\xec\xdd\xbb\x93\x0d=]\x8b\x02\xbb\x02\x05\x05\x9b8\ -J\x89\xc3\xa0\x10@\xac\x1490hb\x19\x9d\xba\xc0\ -\xb9S\xc7\xd8\xbe\xe9n\x9e\xdc\xd9O[K\xd3\x92\xc0\ -\xd5\x07\x88Bug\xd0\x5c9\xf2\xa4\x96]\xe4\xc4\xf0\ -Q\x1a/\x8d\xf0\x83/?\xca\xd6\xbe\x8f]U\x10\x16\ -\xad \xce\xe2\x9c#\xe6\x83\x0fq\x86w\xa6\xc7\x19\x1a\ -|\x85\xcdkV\xb1\xf7\xebOr\xfb\x87;j.v\ -\x8d\x00\x15\x8a\xd1\xa1\x0a\xb3&2x\xfa(\x97N\x0c\ -\xb2\xe7\xb1>\xbe\xb0\xed\x11V\xac\xb8\xb2\xdb\xf8%\x05\ -\xb4\x062\x89\xcc\x06\xcf\xd9\x99\x0b\xbcz\xf8\x00]\xae\ -\xc23\xdf\xde\xc5G\xef\xe9\xbef`u\x01Zki\ -_\xd5\xcak\x13\xef0\xfc\xaf\xe3\x8c\xbc~\x84/n\ -\xe9\xe5k\xbbv\xb0\xaa\xad\xe5\x9a\xc3\x01\x98Z\xcf\xa8\ -\x07O\x0c\xb3\xffo\xc7\x88\xe7\xcf\xd3\xdf\xb7\x91\x87\x1e\ -X\xfa ,\x0a\x10 \xcb`\x1f\xa0w\xbdKho\x80\x90\x03I\x98\ -6q0\x03\xc6\x08[\xd2\xee\xf6B\x07\xdb\xb2%\xdb\ -t\x9afgd\xaddi\xf7\xdb\xdf\xff\xb0\xbb\x12\xc6\ -\x18\xc3g\x5c\xec\xf4\x8dN\xa7\x83\xeb\xba\xff\x0bL\xb9\ -\x5c\xa6R\xa9d\x03j\xady\xfa\xf4)\x85B\x81R\ -\xa9\xf4\xc9\xc0\x84\x10\xb8\xae\x8b\xe7y<~\xfc\x18)\ -\xe5x@c\x0c\xbe\xef\xb3\xb5\xbdM\xb1PD\x1b\x8d\ -\x14\x02\x84@ @\x00\xc6D\xd7\x80\x10a\x07\xfd\x9e\ -\xc6^\x0f\xdd\x1b\x07(%\x97\x9d\x0e\xcf\x9e=#\xed\ -q#&\x16B\xa0\x94\xc2\xbd\xba\xa2\xdb\xbd\xa2\xd7\xf3\ -\x08\x19\x05\xc5b\x91R\xa9\x84\x88A\xd2\xe74d\x06\ -\xb0\x14\xe18c\x14)\x04Z\xeb\xa4\x9d\x5c\xc0\xb8A\ -iI\xf6\xf6\xf6x\xf5\xf2%\x95J\x85n\xb7\xcb\xd2\ -\xf22?>y\x82\x14\x22\xec+Rr\x148l%\ -:\x11c\x0a\x01\xda\xc0\xfbs(\xd8\xb0X\x06K\x82\ -A\x92U\xc6*\x18\x9e%\xbe\xe7\xf1\xa8\xd1`gg\ -\x87w\xef\xde\xb2\xf7\xfc\x88\xf7\x97\x0e`\xa1\x8cAi\ -\x816\xa0ttDu=PW\xda\x8c\xfc\xf7\xc7\xb1\ -a\xf7\xd8\xb0Y\x17|\xff\x00\xbe]\x07G\x88\xe9\x00\ -\xe3\x22#\xb3:\xb6M\xb1X\xc4q\x0a\x18\x04\x9e\x92\ -\x18$\x81\x86@\x83R&\xa9\x07\x11h\xa0\xa2k5\ -p/\xbaV\x1a\xda]8\xe9\x18\x8e\xcf\xe0}[\x10\ -h\x81\x93\xe1\xa4\xa3\x80Q\x10 \x04\x96e\xb1\xbf\x7f\ -\xc0\x87\x8f-:\x17\xe7\xccW*\xdc_\xf4\x91R\x0d\ -\x997\xf6\xb7\xecs\xdf\xf4\xda\xc0\x8b\xa6\xe1\x97'\x92\ -\xbb7\x05s\x0e\x80\xa4\xd3\x99\x12\xd0\x0c8\xf5\xe6\xd6\ -\x16\xf5;k\xfc\xed\x96yP\xeer\xabZF\x08\x19\ -=!\xa2\xe7\x13\x0f\x8b\xde\x15\x91\xf3\x8b\xa4A\x13\xc1\ -\xc6\x01\xfa\xa8&\x92\xbe\x8c\x01!\xb3c<\xd3\xc4\x00\ -\x0b\x0b\x0bT\xaa\x8b\xb8's\xac-y\x94\x1d\x831\ -&\xf4\xd3\x94RI\x04\xa6\x15L\xd7\xe9Go\xac\xaa\ -\x10\xd7\x00\x14B`L\x08\xe4\x07:\xcaO\x03p\x19\ -&\x1d\x01\x1d\x038\xae/f\x0d\x92\xc1\x14\xeb\xa9\xc8\ -\x94\x22J\x1e\x830S\x00\xe6)\x14\xf7\x95\xf5D\xb6\ -\x89E?\x8f\xf9j\xb0\xa3\x81\xe0\x18\xcc\x7f\x199q\ -\xacZS\xdc\xcb\x07\x8c:\x8f\x9d>\x04\x1c\xa3\xd4\x18\ -\xa8tg\x93\xd4\x9bT2g\x92X=\x22@\x13\xcd\ -\x04i\x90\x11%\xa7\x04\x9b\x16<\xdf\xc4Q\xce\xf1U\ -8q&SX\x0an\x12\xd8\xbf\x81\xb5'\xbf\x94V\ -pz\xb8i\xc0&\xad\x97s\xa3\xd8D\xed\xc7A2\ --\xdc,*\x8e\xf3\xdb\xa9\x00\x13\x13'\x0a\x0e\x9b8\ -+Z?I\x90$ZE\xd1\xd27\xb1 \xed\x83\xb3\ -\x80]G\xc5\x5c\x05\x89\x14\xf4\x828\xe1\x08LF\x07\ -\xff\x95\x8a\xd9y0\x91\xb1\xaf`2\x93LP\xe3:\ -*\xce\x048\xbc\x93\x10\xfd4\x13\xafX\xa6\x08\x8e<\ -\x15\xd3\x91{\xfd \x89\x0c\x1a\x07I\x18\xda\x93\xd5\x1a\ -W\x8f\x17\x1e\xf1\x91\x06TZ3.\xe3d/\xf9\x07\ -~}e\xc2 \x89\xc0\xb3\xde\xc9\xaak\x1d\xae\x86\xb4\ -\xd6(\xa5\xd0Z\xa3\xb5N\x9e\x93R\xe2\xf9\x01f\x8c\ -\x87OH3ap\xf8\x01#\xa3\x9b\xe4_\x83pJ\ -)\x94R\xf8\xbe\x9f\x1cA\x10$;9\xc7)\xd0\xee\ -\x5cb\xb4\x19\x19\xff\xe44\x03\xd1r\x8b\x81\xc8\x9e\x0e\ -4\x86\xf3}\x9f^\xaf\x87\xeb\xba\x5c\xba.\xdd\xab.\ -\x9e\xe7\xa1\x94\xc2v\x1c\x0as%\xde\xbc9b\xfd\xde\ -\x97HiM\xa9`\xda\xc4\xa6\xef\x82\xd3Dg\x0c\xe7\ -y\x1e\xddn\x97v\xbb\xcd\xd9\xd9\x19\xe7\xed\x0b\x5c\xd7\ -\xc5hMe\xbe\xc2|\xc1\xe1\xf8\xed\x11_\xdcY\xa5\ -\xd1h\xcc`b\xfa@\x89\x82\x13\xa0\xe2{\xb1\xbf\x05\ -A@\xaf\xd7\xa3\xddn\xf3\xa1\xd9\xa4\xd5jaY\x16\ -\xd5j\x95\x95\x95\xdb\xd8\x96\xe4\xf5\xd1k\xd6\xd6\xee\xb2\ -\xbd\xb5=\xf4\xc9c2\xa0\x08\xf1\x84\x08\xb7\x8b\xb9\x03\ -I\x81\xc6\x80\xbe\xef\xe3\xba.gg\xe7\x9c\x9c\xb4X\ -]\xad\xb3\xbd\xb5\x85\x10\x82\xd3\xd3\x16\xfb\x07\xfb\xd4k\ -\xablnn\x8e\x85\x03\xb2\xb7\xf4\x82p\x0f\xfb\xa1\x1d\ -\x026\xdb\x06Oen\x1d\x86@\x8d1\x89y;\x9d\ -K\xda\xed6\xae\xeb\xb2xs\x11\xdb\xb6i\xb5N\xd8\ -?\xd8\xa7\xb6R\xcb\x85\xcb\x05\x84\xf0\xb3\xc4o\x07\x8a\ -\xdf_)~\xfd3\x88\x229\x7f&\x88\xf3\x5c\x08\xe8\ -suuE\xe7\xf222\xed<\x1f?69x~\ -@m\xa5\xce\xc6F>\x5c\xae\x89\x0d`K\xc1\xc3\x15\ -\xc9E\xd7poI2\xe7\x84\x1b\xefI\xa5\xef\x83>\ -=\xaf\x87\xd7\xebq\xfb\xf62\xdd^\x97\x17\x87\x87\xd4\ -ku\x1a\x8d\x8d\x89p\xb9\x80\x82\xf0;\xca\xce\x1d\xc9\ -w\x0f,\xbe\xb9?\x1c\xfeY\xd3\xd3\xe0l\xa1\x94F\ -\x05\x0a\xa7P`\xa1Z\xe5\xc5\xe1!\xb5Z\x9dF\xa3\ -1\x15\x5c\xae\x89\x0d\xa0\x8d\xe1\xd6\x0d\xf8\xf9\x07\x87\xaf\ -\x96\x04\x03\xc9?\xb3\x0co\x07@Z\x16\xd5\x9b\x8b4\ -\x9b\xcd\x99\xe1\xc6*h\x8cAJ\x89c\xdb\xc9W\x84\ -\x9f\xbe\x06\xdb\x1a\xde|\xe7\xadf\xa4e!-\x0b\xbb\ -P\xc4.\xdd\xa0\xf5\xd71\xeb\xf7\xbe\x9c\x19n\x040\ -\xcea\xbb\xbb\xbb\x94J\xa5\x10\x10\x86\xa7\x95h\x10y\ -&\xd6\xc6\xe0+\x8d\xe7\x07t:\x1d\x1a\x0f\xd7\xd9\xd8\ -\x98\xce\xe7F\x06\x9c\xfe\xca\x7fzz\xca\xc5\xc5\xc5\xcc\ -\x0de\x95R\xa9\xc4\xf2\xf2\xf2\xb5\xe0\xc6\x02~n\xe5\ -\x1f\x17\xf7\x97\x9b\xb7\xd04\x1a\x00\x00\x00%tEX\ -tdate:create\x00202\ -3-03-08T17:27:26\ -+00:00\x9e\x98\xa5\x85\x00\x00\x00%tE\ -Xtdate:modify\x0020\ -23-03-08T17:27:2\ -6+00:00\xef\xc5\x1d9\x00\x00\x00(t\ -EXtdate:timestam\ -p\x002023-03-09T06:\ -18:41+00:00=\x00\x9f\x01\x00\ -\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x03\x89\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01G\ -PLTE\xff\xff\xff\xfe\xfe\xfe\xf2\xf2\xf2\xef\xef\xef\ -\xf8\xf8\xf8\xd5\xd5\xd5\xe2\xe2\xe2\xe3\xe3\xe3\xe3\xe4\xe4\xe4\ -\xe4\xe4\xe5\xe5\xe5\xe6\xe5\xe5\xe6\xe6\xe5\xd6\xd6\xd6\xf7\xf7\ -\xf7\xd9\xd9\xd9\xea\xeb\xec\xe2\xe7\xec\xdd\xe4\xed\xe9\xec\xee\ -\xec\xee\xf0\xea\xec\xed\xcb\xcc\xcc\xc6\xc6\xc6\xe0\xe2\xe2\xf1\ -\xf3\xf4\xf0\xf2\xf3\xe8\xed\xf4\xe5\xeb\xf3\xf2\xf3\xf5\xda\xda\ -\xda\xe0\xe5\xed\x93\xb9\xee\x82\xb0\xf1\xb4\xce\xf1\xbe\xd4\xf3\ -\xc1\xd3\xec\xb8\xbc\xc1\xc8\xc8\xc8\xbf\xc9\xd6\xce\xdf\xf7\xc3\ -\xd8\xf5\xa1\xc4\xf4{\xad\xf3\xcb\xdc\xf5\xdb\xdb\xda\xec\xee\ -\xee\xe0\xe6\xee\xd9\xe3\xf0\xe9\xed\xf1\xec\xef\xf2\xea\xec\xef\ -\xc9\xc9\xca\xc4\xc5\xc5\xe0\xe1\xe3\xf2\xf5\xf7\xf1\xf3\xf6\xe7\ -\xee\xf7\xe1\xea\xf6\xf3\xf5\xf7\xdb\xdb\xdb\xef\xf0\xef\xf1\xf1\ -\xf1\xf3\xf3\xf3\xf4\xf5\xf5\xf5\xf5\xf5\xf6\xf6\xf6\xf9\xf9\xf9\ -\xfa\xfa\xf9\xf0\xf1\xf1\xf3\xf4\xf4\xfa\xfa\xfa\xfb\xfb\xfb\xf1\ -\xf2\xf2\xf6\xf7\xf7\xfc\xfc\xfc\xf9\xfa\xfa\xdc\xdc\xdc\xf4\xf4\ -\xf4\xfd\xfd\xfd\xda\xdb\xdb\xfe\xff\xff\xd8\xd9\xd9\xf7\xf8\xf8\ -\xee\xee\xee\xde\xde\xdf\xd8\xd9\xda\xcf\xd1\xd1\xea\xea\xea\xcc\ -\xcd\xcd\xc3\xc4\xc4\xc9\xca\xca\xed\xee\xee\xf2\xf3\xf3\xd0\xd0\ -\xd1\xc5\xc6\xc6\xe9\xe9\xe9\xd4\xd5\xd4\xf8\xf9\xf9\xbf\xc0\xc0\ -\xcb\xcb\xcc\xd7\xd7\xd6\xe8\xe8\xe8\xc1\xc2\xc3\xcb\xcb\xcb\xf0\ -\xf0\xf0\xd3\xd3\xd3\xd5\xd7\xd8\xc8\xca\xcc\xe5Kfq\x00\ -\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\x09\ -pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\ -\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\ -\x1f\xc9N\xa7\x00\x00\x01\x06IDAT\x18\xd3U\xd0\ -WS\x021\x10\xc0\xf1\xcd]$\x1eJ\xec]Q\xb0\ -\xf7\x86\x1d\x85\xbb\x08wr\xc0YP\xb1c\xaf\xdf\xff\ -\xd9$\x1b\x1f\xfc?\xfefwvf\x01\x88e\xff\xcb\ -\x22\x00@\x9bb1\xc6\x9a\x9d\xb8\xaa\xa55\x9e\xa0\x12\ -y[{GgWwOo_\xff\xc0\xe0\xd0\xf0H\ -\x92k\x1c\x1dK\xa5\xc7'&\xa7\xa6gf\xe7\xe6\x17\ -\x16\x11\x97\x96WV\xd7\xd63\x1b\x9b[\xdb;\xbb{\ -YD{\xff\xc0\xca\xe5]\x8fS*\x0e\x05b\xb2P\ -\xb0\x8a\x8a\x84/\x0b\x0c\x1eI+)\x0ad\xa1\xc1\x5c\ -\xd1\xe5\xb4,)TU\x10\xab\xf9\x12\xd5\x14\xa9\x10\x8f\ -]O\xeej#2\xc4\xac\xc7\xd1\x08\x01BN\x0cr\ -j\x8c\x84\xc2\xe3\xa7\x88gjP\x99\xa8\x9d'..\ -}\x8dT\x04\x1a\xfd\xfa\xd5u\xe6\xe6\x16\x10\xcb\x81:\ -\x12\xdd\xdd?4\x1em@|\xd2\xdb\x10\xd6\x9f_^\ -\xd1\x80W\xd0 z{\xff\xf8D\x03\xfa\xc5\x98\xe3\xc8\ -\x1f\xb3\xef\x1f3\x07@j\x8d\xbf\xaa\x86~\x01v\xa9\ -<\x5c=\x11\xff\x81\x00\x00\x00%tEXtda\ -te:create\x002023-0\ -3-08T17:27:26+00\ -:00\x9e\x98\xa5\x85\x00\x00\x00%tEXtd\ -ate:modify\x002023-\ -03-08T17:27:26+0\ -0:00\xef\xc5\x1d9\x00\x00\x00(tEXt\ -date:timestamp\x002\ -023-03-09T06:20:\ -50+00:00\xdd\xe8\xb2\xde\x00\x00\x00\x00\ -IEND\xaeB`\x82\ -\x00\x00\x03\xf2\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xe0\ -PLTE\xff\xff\xff\xf9\xfa\xfc\xde\xe5\xf1\xed\xf1\xf7\ -\xfd\xfd\xfe\xe9\xee\xf6\x97\xb0\xd4\xa0\xbe\xdd\xab\xc3\xde\xea\ -\xee\xf5\xcd\xd8\xea\x83\xa2\xcc\x9a\xbb\xda\xbb\xda\xec\x93\xb3\ -\xd2\xe3\xe9\xf2\xf5\xf8\xfb\xab\xbe\xdb}\x9e\xc7\x9f\xbf\xd8\ -\xac\xcb\xdf\xaa\xc9\xde\x8a\xa9\xc8\xe4\xe9\xf1\xe0\xe7\xf2\x8b\ -\xa7\xcc\x7f\xa0\xc5\x9c\xba\xd2\x9e\xbd\xd4\x9e\xbc\xd4\x83\xa1\ -\xc1\xe3\xe9\xf0\xfd\xfe\xfe\xc0\xcf\xe4u\x95\xbf\x80\xa0\xc0\ -\x92\xaf\xc9\x93\xb1\xcb\x94\xb3\xcd\x95\xb4\xce\x95\xb4\xcf}\ -\x9b\xbd\xe3\xe8\xf0\xef\xf3\xf9\x9a\xb1\xd1f\x88\xb1{\x99\ -\xb7\x84\xa2\xbe\x87\xa6\xc3\x8b\xaa\xc8\x8e\xad\xcb\x8f\xaf\xcd\ -\x8f\xaf\xcey\x97\xba\xe3\xe8\xef\xd5\xdf\xecu\x92\xba[\ -|\xa2m\x8b\xa9u\x93\xb2{\x9a\xba\x81\xa0\xc1\x86\xa6\ -\xc8\x8a\xaa\xcc\x8c\xad\xcf\x8c\xad\xd0x\x96\xba\xe3\xe7\xee\ -\xc5\xd1\xe4Tu\xa2Qo\x92[x\x98c\x81\xa3l\ -\x8b\xadt\x94\xb8|\x9c\xc2\x83\xa4\xca\x88\xa9\xcf\x8b\xac\ -\xd2\x8c\xad\xd4y\x97\xbc\xd0\xd9\xe8]z\xa26U}\ -A_\x85Sr\x99a\x81\xa8m\x8e\xb7x\x9a\xc3\x82\ -\xa3\xcc\x8a\xab\xd3\x8f\xb0\xd8\x91\xb2\xda}\x9a\xbf\xe3\xe7\ -\xed\xe4\xe9\xf0x\x8f\xae,Lz0R\x83Df\x98\ -Vy\xacf\x8a\xbdu\x98\xca\x82\xa4\xd4\x8b\xad\xdc\x8e\ -\xb0\xe0z\x98\xc0\xe3\xe7\xec\xf9\xfa\xfb\xaa\xb8\xcbIh\ -\x92>c\x96Pv\xac`\x86\xbcp\x95\xca\x7f\xa3\xd7\ -\x8e\xb1\xe3\x94\xb7\xea|\x9c\xc4\xe3\xe6\xeb\xd2\xd9\xe2l\ -\x86\xa5Ls\xa4a\x87\xbdq\x95\xca\x80\xa4\xd7\x8e\xb1\ -\xe4\x95\xb8\xea{\x9d\xc4\xe2\xe6\xeb\xee\xf0\xf3\x93\xa5\xb9\ -U{\xa5g\x8f\xc3y\x9d\xd2\x83\xa6\xda\x86\xaa\xdet\ -\x99\xbf\xe2\xe6\xea\xfd\xfd\xfd\xbc\xc5\xd0b\x83\xa3`\x8c\ -\xbcu\x9a\xcfv\x9b\xd0k\x93\xb8\xdf\xe3\xe7{\x94\xaa\ -R\x7f\xa9c\x8d\xc2a\x8c\xb2\xe2\xe6\xe9\xf7\xf7\xf8\xa2\ -\xb0\xbcQ{\x9b`\x8f\xac\xe6\xea\xec\xda\xdf\xe3\xcf\xdc\ -\xe2\xfb\xfc\xfcO\xd6>\xde\x00\x00\x00\x01bKGD\ -\x00\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\x00\x00\ -`\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07tI\ -ME\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\x00\ -\xd6IDAT\x18\xd3c` \x04\x18\x99\x98Y\xd0\ -\xc5X\xd9\xd898\xd1\xc4\xb8\xb8yx\xf9\xf8Q\x84\ -\x04\x04\x85\x84ED\xc5\xc4\x91\xc5$$\xa5\xa4ed\ -e\xe4\xe4\x11B\x0a\x8aJ\xca*\xaaj\xea\x1a\x9aZ\ -p1m\x1d]=}\x03C#c\x13S3\x98\x98\ -\xb9\x85\xa5\x95\xb5\x8d\xad\x9d\xbd\x83\xa3\x933T\xcc\xc5\ -\xd5\xcd\xdd\xc3\xd3\xcb\xdb\xc7\xd7\xcf?\x00&\x18\x18\x14\ -\x1c\x12\x1a\x16\x1e\x11\x19\x15\x1d\x13\x1b\x07\xd3\x1e\x9f\x90\ -\x98\x94\x9c\x92\x9a\x96\x9e\x91\x99\x95\x0d\xb7('7/\ -\xbf\xa0\xb0\xa8\xb8\xa4\xb4\xac\x1c\xc9\x99\x15\x95U\xd55\ -\xb5u\xf5\x0d\x8d\xc8\x8eojnimk\xef\xe8\xec\ -B\xf1fwOo_\xff\x84\x89\xa8\x82\x0c\x0c\x93&\ -O\x99:m:z\xd0\xcd\x989k\xf6\x1c\xccP\x9e\ -;o>\xce\x18\x00\x00\xa7\xd82\x9a\xd1%ng\x00\ -\x00\x00%tEXtdate:crea\ -te\x002023-03-08T17\ -:27:26+00:00\x9e\x98\xa5\x85\ -\x00\x00\x00%tEXtdate:mod\ -ify\x002023-03-08T1\ -7:27:26+00:00\xef\xc5\x1d\ -9\x00\x00\x00(tEXtdate:ti\ -mestamp\x002023-03-\ -09T06:20:50+00:0\ -0\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\xaeB`\ -\x82\ -\x00\x00\x08\x19\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\ -\x00\x00\x00\x04gAMA\x00\x00\xd6\xd8\xd4OX2\ -\x00\x00\x00\x19tEXtSoftware\ -\x00Adobe ImageRead\ -yq\xc9e<\x00\x00\x07\xabIDATX\xc3\xad\ -W[P\x93g\x1a\xf6\xca\xce\xec\xcc\xf6b/\xbc\xd9\ -\xe9\xce\xecn\xbd\xda\xd9\x9b\xb5\xce\xba;{\xb0\xad\xcc\ -z\xb1\xce\xce:\xb3vTpu\xdb\xe2\x81\xd6\xb6T\ -\x04\xbb\xa5 m\xc1\x82\x06\x08\x07QB\x80\x80\x80\x02\ -!\x81\x10\x92@H\x10s$!gr\x80\x04B \ -\x9c\x09G\xb5Tx\xf6\xfb~\x13\x160X\x8b}g\ -\x9e\xf9/\x92\xfc\xcf\xfb>\xcf\xfb\xbe\xdf\x97]\x00v\ -\xfd\x98 \xf1\x0b\x82\x14\x02\x03\xc1u\x82\x03\xcf\xfd\xfe\ -\x8fH\xbc\x9b \xe1W\xaf\xef\xb5*\x8c\xd6e\xdb\x02\ -`\x19\x1e[\x09'\xf13\xfa\x19\x81\x22\xfc\xdc>v\ -H~\x8a\xa0\xb9\xb6Y\x1c2\xcf\xadB9\xfe\x1dD\ -\xf6Q\xd8\xc7\xe6\xe8\x87\x86={\xf6XSR\xae,\ -\xca::\x10N\xe2\xe5I\xc3\xc41\x04\xb7>I\xf9\ -,`\x9b]YSM\x03M\xb6\x114\xeb\xfb 1\ -y`\x19\x9d\xc5\xbb\xef\xbe?\xc5\xab\xbe\x83\xf1\x89)\ -LO\xcf\xae\x92\xef\xd7\xbct\x02\x11\x9f\x0f\xbe\x1d\xe3\ -\xb2\x04CO\xb43@\x8b{\x06\xcd=.4\xeb\xec\ -\xa8W\xf6 \x87S\x852^5C\xbc\xb0\xf4\x90\x81\ -\xc1`\x5c&\xbfK|\xe1\x04H\x1c$8A\xfd\xdd\ -\xeas'\xf1\xb9'\x04H\x87\x97\xc1\xd7\xbb \x22U\ -7\xdc7\xa2\xb8N\x88,V>\xccV\xdb:q\x04\ -,\x16k,\xfc\xce\xe7'\x10\x916\x93\x95?F}\ -\xa5\xfe\x12\xc4o\xf4Y1\xb6\x02~\xef Z{\x9c\ -\xe0?0\xa1L(CF\x0e\x1b\xb2\x0e\xf9&\xd2\xf9\ -\xc5e\xcc-,!4\xbf\x88\xbd{\xf7Z\xc9;~\ -\xbam\x02$~C\x90F=5\x13iu\xb3\x80\xd2\ -?\x0f\xcb\xc4\xe2\x9aP\xa1Z\xb4l\xf1Y\xa0\xb6\xa0\ -\xa6]\x8d/\xb2sq\xb7\x9e\xff\x0c1%\x9d\x09\xcd\ -cbj\x06\x83C\x81'\xe4\xdd\xbc-\xd3\xb0;\x92\ -\x033&\xd4S\xb5\xd3\xfbXO\x88\xc5\x03!\x88,\ -CP\xbaF\xd0\xed\x09B\xe5\x9bB\x9bs\xfc\xa9\xcf\ -Z\x1b\xee*t\xc8\xbc\xc9E\x09\xa7l\x93\xcf\x9b\x88\ -'\xa7\x11\x18\x1d\xc3\x80o\x08\xa2\xd6\xd6%\xc2Q\xdb\ -(\x12\x87\xc6\x1f\xaf\x82/b\x94M\x89$\x90\x22\xea\ -R-\x9aB\xab\xe8\x18y\x04\xa1\xc5\xcf\x10St\xf6\ -\x0d\xa3\xd3\xe1\x87\xd4<\x80\x16\xbd\x03\x0d]\x06\x14\xd5\ -\x0a\x90\x91\x95\x0d/y\xf1\xc6\xaa\xa9\xd4\xb3s\x0bL\ -\xc5\x94\xd8\xdd\xef\x85\xc9b\x05\xb7\xbc\x12\xa5\xe5\x95K\ -\x13\xf3\xcb\xab#\x0f\x017\xd9\x11\xe6\xd9\x15\x84\x97\x15\ -\x13\x06\xcb<\xd0h\xf2\xa3\xdd\xee_'\x96;\x86 \ -\xb3x\xd7}\xe6\x08\xa4\xf8<3\x1b*\x8d6\xaa\xdc\ -S3!\x8c\x8e\x8d3\x15\xd3&\xe47\x09\xf1\xc1\xc5\ -\x8fQs\xaf\x01\xbee`\xfc\x11\xa0#\x13#\xf2\xce\ -\xa1\xbe]\xb9\xb8Q\x01\x83\x81ttM\xa7\x1e\x0ag\ -\x80\xa9\xb8\xdd\xea\x83\xd8\xe8B\x93\xca\xcc\xf8|\xe5\xcb\ -,\x88\xda$Q\x89\xa7g\xe7\x18\x1b\x86\x86G`w\ -8I\x82:$|\xf8!\xae\xb3\x0b\xe1\x99\x5c\x80o\ -\x09\xd0\x90\xde\xe1\x0f,\x81\xab\x1f\xc4}\xef\x04\xdd\x07\ -\x1da\xeb\xff\x9f\xc0\x1d\xb9\x16\x1d\xf6!H\xcc\xfdO\ -}\xee\xd4\x22\x9dU\x84\xaa\x9a\xbaM>G\xe4\x8e\xf8\ -<<\x12\x84\xd3\xdd\x0f\xbd\xc1\x88\xc2\xe2b\x9c~/\ -\x1e=\x03\x01\xf4/\x02\x83\x84\xbc\xc5\xff-\xee:C\ -(Q\x91\xf7\xf6\x05\xf1N\xdc\xbf}\x843i\xe3 \ -\x18\xf43\xab\xe0\xc9Th58\xd1\xd8\xdd\x0b\x9eX\ -\x89\xac\x5c\xf63>G\xaa\x9e\x9c\x9ee\xe4\xee\xf7\x0e\ -\xa2\xd7lAC\x03\x1f'b\xe3 \xe9\xd6\xc0E\xcf\ -\x01R\x90$\xb8\x86\xb2\x9e\x00n\xb4\xdbP\xd1\x1bD\ -\x85\xce\x8bJ~\x0bm\xbe\x9b['\xd1\xa0\x99\xf8\x16\ -e\x22\x05\xee)\xf4(\x13\xc8\x90x5\x0b\x1a\xad>\ -\xaa\xdcc\x13\x93\xf0\x0d\x0d\xc3f\xef\x83\xb4]\x8e\xc4\ -K\x97\x90\xc3\xca\xc3\xd4c\xc0NzI1N\xfa\x89\ -\x94\x7f[;\x84|\x85\x13%j\x1fJ\xd5\x03\xe8\xf2\ -0\xa3(\x22\xf8\xf93\x09t\x8f.\xa1\xa8\xbe\x15\xa5\ -|\x09\xb2J*\xf0\xcf\xe3qQ\xe5\xf6\x07F\xd1\xe7\ -\xf2@\xab7 \xfdj\x06\x92\xbfH\x83\xcd7\x02'\ -\xa9\xda@\x1aL\xe0{\x88R\x9d\x1fE\xdd\xfd\x0cq\ -A\x97\x1b\xc5\xdd\x1e\x88\x9cA\xfc\xf9\xcd\xb7]\x84\xeb\ -l\xb4C\xd0(\xf7N#\xa7\xfc\x1e\xb2K\xab\xf1Q\ -\xeaWH\xfeo\xea\xfaXQ\xb9G\x82\xe3\xf0\x0c\xf8\ -`4\x99Q\xc9\xab\xc2\xfbg\xcfA\xfe@\x03?\xe9\ -n\xb2\x8d\x19\xb9oi\x06\x19\xd2\x9b*/r\xe5\x0e\ -\xe4u\xf6\xa1\xf0\xbe\x1b\x1c\x95\x1b\xf9\x9c\xca)\xc2S\ -\xb8\xdd)\xdc+v\x04\x90Q\xc8\xc5\x95ky8\x11\ -\x9f\x80\x9b\xb7n3c\x15\x91\xdbjs@\x22m\xc7\ -\x85\x84\x0fPt\xbb\x0c\xf3+\x80\x9f4X\xf7$ \ -\x1c|\x84J\xd3\x188\xfaa\x86\x9cV\xfdU\xb3\x1e\ -\xac\x0e;\xb8:\x1f\xd9!\x1ez/\xe0\x13\xbc\xba]\ -\x02&\xbe\xc1\x83\x94o\xd88\x9f\x9c\x8a\x03\x7f=\x04\ -c\xaf\x99\xe9n*\xb7F\xd7\x83\xa4\xcb\xc9H\xff:\ -\x8b\x8c\xd5\xc7\xd1\xd83\xf881\x09\x86^\x13\x1a\x9b\ -\x04\xf8\xdd\x1b\xfbQO\xd4\xf1\x90\x99\xee\x9a\x00\xaa\xad\ -\x93`+]\x0c9\xf5\xbc\xf0\xbeg\xbd\xea\xcc\x16=\ -JU\x1e\x08m\x01\x94\xd4\xf1C\xe1eS@\xf0\xca\ -\xf7%`+nj\xc7\xa9\x84D\xc4\x1c9\x8a\xdc|\ -6ZZ\xc58\x14\x13\x83/95\xc8\x14j\x98\xe6\ -\xa2\xd5\xd2'\xf5\x9azL\x13\xa1Id\xb7\x99\x90\xdb\ -nF\xb9\xda\x8d\x06\xa5v9,9=\xf9N\x13\xec\ -\xd9r\xd4G\x0d;\xabF\x88c\xff9\x8f\xdf\xee\xfb\ -=\x1a\xf9\x02\x9c\xbf\x90\x80\x93\xf1\x17p\xa3\xad\x07\x19\ -\xc4OJ\x14\xe9n\xbaX\xa8\xef,\xfa\x94\x98P(\ -\xb7@\xe9\x0e<\xf9W\xec)*w-\xc1g\x04\xfb\ -\xb6\xb9\xe4D\x8d\xbe\xcc\xb2Z\xfc\xe3\xe4\x19\x1c<\xf4\ -7\xb0r\xf3\xb0\xef\xc0\x1fP \xd1!\x89'e*\ -\xa6K\x85>\xbf!\xd5F\xe4.\x90[!\xb0\x0c\xae\ -\xe5\xdc\xe2\xd2\x11\x13\x13\xe4\x87o<\xaf<\xe7\x96\x15\ -5\x9ciE\xe5\xf8\xfb\xb1X\x1c?\x19\x877\xf6\xef\ -\xc7\x8d:\x11\x92\xab\xa4\x0c!\xedp\xea5U!\x8b\ -4[\xc9\x037*4n\xd4I:\x17\xc3rs\x08\ -\x8em\x95\xfb\x87$\xe0Jesp\xe4\xf8)\x1c>\ -|\x98\x8cc.2\x05*\x5c\x22\xd5\xd3]~M\xdc\ -\x0b6\xe9tv\xa7\x1dw\x8c\xe4\x88\xb6\xf9\x9e\x84\xb7\ -\x1a\x95\xfb\x22\xbdI\xfd\x80\x0bm\xf4\x042JxL\ -\x0f\x9cKI\xc3\xb5\xa6.|\xc2me6Y\xf1\x83\ -\x01\x5c\x97\x9a\xc1Q{ \xf3\x04\xd7\xce%&\x056\ -\xc8\xfd\xc7\x9d\xc8\x1d\xd5\x82\xdc\x1a\x01\xce^NE\x81\ -X\x85x\xf6]\x5c\xa9U\x90\xaa\xfb\xc0\x96\xdbP\xad\ -u\xe3\xaeTA/\x10\xca\x0dr\xbf\xba\xd3j\xa3\x05\ -\xb7\xa2Q\xf8\x1d\xafC\x8dO\xb9-\x88\xcb\xe6\xe1\x9a\ -H\x8f\xaa\x1e/\x9a5\xe6\xc7\x7fz\xf3-Wx\xac\ -\xa8\xdc\xaf\xbd\xac\xdc\xd1\xe2\x08\xdd\x05\x5cu\x1f\xde\xcb\ -\xafE\xb9v\x002g`\xf5\xc2\xa7\x97\xa9\xdc\xf7\x08\ -\xd2\xa9\xdc;\xf8\x03\xf3\xc2\xf1\x13\x82\xca\x1c\xee\x9dP\ -\x0b9\x94\xb8\x0d\xc2\xc8\x16\xa3\x17\x87\xc3/\x22\xf7\x0e\ -\xff\xdam\x8a\xdda\x99\xd5\x1b\xb6\xd8k\xbb^2\xbe\ -/\x89\xff\x01f\xb9_\xfc\x11\x80=\xcf\x00\x00\x00\x00\ -IEND\xaeB`\x82\ -\x00\x00\x09\x08\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\ -bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\ -\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\ -kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\ -\x12)\xc3\xf6 \xcd\x00\x00\x07\xd3IDATX\xc3\ -\xbd\x99Ol\x1cW\x1d\xc7?\xef\xcd\xec\xec\x1f\xaf\xbd\ -v\x9c\xc4n\x12\xb7n\x1dg\xbdN\x9c\x16\xb7\xa5!\ -9\x00\x95(\xff\x0e\x15\x08h/\x15E\x02\x89\xaa\x1c\ -\xe1\xc0\x01!\x0e\x5cz\xe2\xc8\x81\x03\xe2R\xa9\xaa\x80\ -\x22\x04\xad\x90\xf8s)T\xfd\x13\xb5\xa9\xd2\xd6N\xeb\ -\x06\x928\x7f\xbc\xde\xf5\xfe\xf1xf\xde\x1f\x0e\xeb]\ -\xef\x8ew\xecMD\xf9I\xa3\x99y\xde}\xf3\x99\xef\ -\xef\xfb{\xef\xed\xb3\x08\x82\xc0\xd6j5\xb4\xd6\xfc\xbf\ -\xc2u]FGGq\x1cg\xff\xcf\xd6j5.^\ -\xbcH&\x93AJ\xf9\x89\xc3i\xad\x89\xa2\x88\xc5\xc5\ -Er\xb9\xdc\xfe\x80Zk2\x99\x0c\xf3\xf3\xf3\x18c\ -A\x80h\xffU\xb4\xae\x12\xef\x93\xda\x12B\x08A\xa4\ -\x14\xef^\xb8\x801f0\xb5\x01\xa4\x94\x18k\x09\xa3\ -\xb0\xf5 !\x10\xed\x07\x0b\xb1\xd3\x16;w\xc0\xf6\x00\ -\xefi\xb2\x16\xb9\xdd\xb7\x10\x83\xbc\xd26`\xf7\x1b~\ -\xa2\x80\x80\xb5\x960\x0ci4\x1a\xb0}\xdf/\xa4\x94\ -\xa4\xd3\xe9\x1d@\xb1\x0d\xd3\x0f\xf4\x7f\x0d\xb8\xba\xba\x8a\ -R*\xd1\x83\xd6Z\x8c1\x94J\xa5.\x05\x93\xa0\x92\ -\x00\xbb^\xa8\xbb\xad\xa3\x80\x90\x08!0\xd6b\xb1\x1d\ -@)%\x93\x93\x93\xcc\xcd\xcd\x91\xcf\xe7\xb1\x09\x80\xef\ -]\xbc\x88R\xaaWA\xba\xfc!\x92\xa0cJ\xf7\x03\ -\x94B\xf2\xd1\xfa\x87\x5c\xae\xac\xf0\xe0\xb1\x87)dF\ -1v\xa7(\x84\x10\x08)w\xfa\x89\xbf\x9c\xe3t\x86\ -\xa0^\x05\xf7Q/\x0e\x96\x08(\x1d\xfeu\xf9U^\ -\xfb\xf7\xab\x9c\xbf\xf2:_\x9d\x7f\x9c\xb9\x89\x93=\xa9\ -\x17}\xbe\xd7/:\x03_\x1b\xa0s\x8e_\xdf\xc6!\ -\x85@\x19E\xa4\x03\xaa\xe1M\x9e\xfb\xeb\xcf\xf9\xcb\xfb\ -\x7fF\x19\x85\x14\xb2\xf3\xf2r\x8f>\xda\xd1[\xc5m\ -Ub\x80\x0c\xaadWG\xc6X<\xd7\xe5\xa9\xb3_\ -\xe0o\xef\xbd\xc1\xaf\xdf\xf8\x15KkK|c\xe1\x09\ -\x9cm\x7f\x0e2\xd4\xecJq\x12\xdc\xae\xeb>`\xdd\ -\xed\xc6B\x10\x85\x04a\x83s\xc7g94\x9c\xe7\xe5\ -\x0b\xffd\xe9\xfa\x12gF\xce1/\xe7\x91B\xb6\xbc\ -\xd9\xd5O\xdc\xe7=)f\xbf\xd4\xc6\xda{L\x1f\x83\ -\xb5\xc6\x12\xaa\x88\xf5\xc6\x1a\xeb\x8d\x9bL\x0c\xa7\xf8\xda\ -\xe2\xa7\x18\x1a\xf2y\xe9\xca\x8b\xbc\xb2\xfc\x0a\x81\x0ep\ -\x1c\xa7\xc7\x1a\xf1~\xfa\x0e\xd4\xa2[\xcd\x18@\x1c\xac\ -_\x08!0\x06\xa2(\xa4\xbe\xb5\x06\x22\xd8\x06\x80\xb3\ -3wq(\x9f\xe2\xb7\xef\xfe\x86Kk\xcb|\xfb\x91\ -\xa79:z\xb4\xa7\xca\x93Sl\xed@p\xfd\xc0\xe2\ -mJ\x19\xc2 \xc0\x8f\xca\x18|\x1c\xb9\x93\xc0\xa9\x03\ -\x82\xa1\xf4\x01^\xff\xf0\x1f\xfc\xf4\x0f\x97\xf8\xce\xd9\xef\ -qf\xe6\x11$rg\xcc\xec\x97\xe26\x18\x09iN\ -\xf2]\xbf\x22\xd1\x91e\xd3\xaf\xd0\x0cW\x09\xcd:\x91\ -\xad\x10\xd92\xa1Y\xc3\xd77\xf1R\x1b<4\x93a\ -$\xbf\xc2/\xff\xfe\x1c/\xbc\xf6\x22\x91\x89\xf6\xae\xe2\ -\x0e\xd8\x1e^\xdb+\xbd\xddmF[\xea\x9bW\xb9R\ -[&\x9b\xce\x90\xf5<\x1c\xc7\x05\xeb\x10)K\x10j\ -\xb6\x02M:mI\xbb\x967?z\x8b\xaf\xdc\xff%\ -\xbc\x9c\xd73?'\xa68\xa9B\x93T\x8c\x87V\x16\ -a4\xb9l@\xca\x0bpR;\xde\xd4\xc6b\xa4e\ -\xbd\xe6p\xe1\x83\x83\xc7\xd9\xf9\xc7\xf9\xe1\x93\xcfr\xf7\xc4\x14\ -~\x10\xa2\x8d\x19\xe8G\xd6.\xc0\xdb\x99)\xba\xdb\x8c\ -1\x18cPJ\x11\x86!Z)F\xb3#\x1c\xcc\x9e\ -\xe0[\x9f\xff:_\xfe\xf4\x17I\x19\x97[\xe52~\ -\xa8p\x5c\x077\x95\x1a\xe8Y{*8\x08h[\xb9\ -(\x8a\x08\x82\x80f\xb3I\xbd\xde\xe0\x5c\xf1a\xe6&\ -\x8e3\x9c\x19\xa2\xba^%\xcaG\x84Jc\xb4\xa68\ -{\x9ct:\xdd\xb1D\xbc\x7fkm\xa7&\xf6M\xf1\ -^\xa0m8\xa5\x14A\x10P\xab\xd5)\x97\xd7X+\ -\x97\xf1}\x1f)$\x9e\xe7166Juc\x03\xa5\ -5'K%\xc6\xc7\xc7[\xd5\xbb\xfd\xfd\xf6\x8b\xb6j\ -\x00\x8c\xdd\x19j\xdc8\xc4 \xc6\xedV\xaf\xbdS\xd0\ -l6\xa9T+\x5c\xbfq\x83Ba\x84{\xa7\xefe\ -\xa40\x02XVVV\xd0Zsj~\x9e\xb1\xb11\ -\x94Rh\xad;G\x1bV\x08\xd1\xda\xdd\x90\x0e\xda\xc4\ -\x00\x07Q-\xae^\x1b0\x08\x02\xea\x8d\x06\xd5\xea\x06\ -\xbe\xef\xf3\xe0\xe2\x22\x87\x0e\x1d\xa2Z\xad\xb0\xb4\xf4\x01\ -a\xa88u\xf2$\x85B\x01\xa5\x14Q\x14\x11\x86!\ -A\x10\x10\x04!JE\x18c\x10B\xe2y)\x8ct\ -\x09\xc3\x10\xd7u\xef\xac\x8a\xdb\xd1N\xefV\x10\xd0l\ -4\xa9\xd7\xea8\x8eK6\x9b\xa5Ri\xc1EJ\xb1\ -\xb0\xb0\xc0\xf0\xf0p\xa7\x88|\xdf\xa7\xd1h\xd0h4\ -\xf1}\x9f \x08\xb0Xr\xd9\x1c\xc2ui4\x9a\x1c\ -;2\xc9\xe8\xe8\xe8`\x1e\xec\x17m\x05\x8di\xad\xfb\ -\xb6\xb6\xb6\xf0\xb7|\x8e\x1e9B\x18\x86,_ZB\ -i\xcd\xfd\xa7\xef'\x9f\xcfw\xe0\x1a\x8d&\x95J\x85\ -r\xb9L\xbd^'\x8c\x22\xb2\xd9,\x87\x0e\x8ec\xac\ -a\xbd\x5cf\xe6\xdei\xa6\xa7\xa7q\x1cgp\xc0~\ -Jv\x17I\x14E\xa4\xdc\x14\x07\x0e\x8c\xf1\xe1G\x97\ -\xb0\x96\x1e\xb8(\x8a\xf0}\x9f\x8d\x8d*\xd7o\x5cG\ -+\xcd\xc4\xc4\x04cc\xa3\xe4\xf3yn\xad\xdd\xe2\xea\ -\xb5k\xcc\xce\x1c\xe7\x9e{\xee\xe9\xec\xb4\xdd\xb1\x82\xdd\ -*Z\xdb\x9aM\x0f\x1c\x1c\xa7\xbaQ\xc5\x91\x92\x85S\ -\x0b\xe4\xf3\xf9N\x11DQD\xa3\xd1\xa4Z\xdd\xa0Z\ -\xdd`\xe1\xd4Ifg\x8f\xe3\xfb>+\x1f\xafp\xed\ -\xda5\x8a\xb3'\x98\x9a\x9a\xea\xd9\x06\xec\xbbX\x88+\ -\x96\xd4&\xa5DH\x89t\x5cr\x851B\xbf\x89\x97\ -\xf2(\x16\x8b\x0c\x0d\x0du\xe0\xdb\xc3P\xb3\xd9\xa4V\ -\xaf\xa3\x94bdd\x84\xcdM\x9f\x8f\xdbp'\x8aL\ -MM\xf5\xff\xd1\xa4\xb5fkk\xab\xef\x8eg\xbbb\ -\x95R\xb8\xae\xbb3{XK\xa44[\xa1\xc28\x1e\ -aX!\x9f\xcbRb*9$\x97\xad=\ -\xe4`o\xccb\x1b[^\x8c\xd7\xe6\x1d\x1bK\xe8\x05\ -\x04BB3\xa3\x99\xceA\xc0:\x87\xf0\xb2\x0b\xc1O\ -\xd5\x94\x0e#\xcd\xfc\xf4t?\xddO\x0b\xad\xb5\xe6#\ -\x0eu\x16\x0f\xf9\xf0?\x0a!.\x1e\xa8\xb5\xc6\xf3:\ -\xb8\xae\x8b\xe3\xb88\xae\x83\xefy \x04\xa6R\x98f\ -\x00\xcb\xb2\x08\x04\x02\xff\x7f\xa0\xeb:4\x9bM\xaa\xb5\ -MV\xd6\xd6)\x96*\xec\xee\xee\xe2{.\xd2P\xf4\ -\xf6\x84\x18\x1a\x8a\x92N\x0e3\x18\x8b\x11\x8b\x0dbY\ -\xd6\xcf\x06\x8a\x93\xceA\xad5\xcd\xe6.\x95J\x95\xc2\ -\xcbyV\xd7\xab\xc4b1r#i\x06\x22\x970\x95\ -\xc2vl\xca\x95\x1aK\xcb\xab\xd4\xb7j\x8c\xa4b\x5c\ -\xfd$\xc7h~\x94h4z~@\xad5\xf5z\x9d\ -\xd5\xb7\xef\x98.,\x10\xbe\x14c\xf2\xf6M\xa2\x91~\ -\xa4\xfc\xef9'\x84\xa0\xd3\xf1X/\x96\x99\x9a~\x8a\ -\xd3\xda\xe4\xe6\xd5\x0c\x13\x13\x13$\x86\x13\xe7\x03\xaco\ -\xd7YZ^\xe5\xc9\xec\x1a\x97\xaf\x5c\xe3\xda'y\x0c\ -C \x04H!@\x80\x10\x80\x06\x0dh_\xa3\x11\xb4\ -m\x97'3\x05\xd6\x96\xe6\xf8tt\x98{w\xef\x12\ -\x8d\xc6N\x05\x94\xc7}\xa1\xddn\xb3\xfen\x9d\x17\xaf\ -\xd7\xc9]\x1e#\x9f\xcf\xe1i\x8d\xdfM\x17\x08\xd1\xad\ -\xdc\xae\x16\x84@\x0b\x81\xaf5\xd2\x90LN\xde&3\ -z\x937\xcbE\x0a\x85\x02\xae\xeb\x9e-\xb0R)3\ -\xbfRD\x04c$\xd3)<_\x83\x10\xb8n\x07\x04\ -HC \x95\xc00~\xba\x10\x9a\x8e\xe7\x81\x90h$\ -c\xd7\xafc\xf5'y\xbfQ\xa1T*\x9e\x1d\xd0\xb6\ -mJ\x1b\x1b\xacUl\xe2\xc94 \x10R\xd2h\xee\ -\xf1\xed\xbf\x1e`\xb7[(S\xa2\x0c\x81R\x02C\x09\ -\x94\x92\xbc\x98\xfd\x91\x17\xb3s\x98\xa6\x814\x04A+\ -@&w\x95V\xc7d{g\x1b\xcf\xf3\xce\x06\xd8h\ -4(W\xeb4\xfd^d \x84\x16\x02aHv\x9b\ -\xbbt00\x03\x0a\xa5\xc04%JILC\xe0\xeb\ -\x0e[\x8d\x16\x81`\x90\x80)\x08\x05$\xc1\x80dp\ -h\x90V'\x88\xe3th\xb5\x9ag\x05\xdcasg\ -\x0fi\x85\xd9\xb55v\x07\x100\x92\x1eb4\x9fE\ -)\x03%\x05\xa5R\x99\xcd\xda\x16J\x0az-\x93\xf1\ -\xb1\xcb\xf4_\xea\xc7T\x82\x80)1\x95D\x18&\xae\ -\x08\xb2\xb2\xf6\x8eF\xa3q6\xc0f\xab\xc9\x9e\xed\x11\ -\xb0\x82\x08)\xb1}\xe8\x00\x9e\x14h\x0ek\x84b\xa9\ -J\xb5V?\xa8\x11\xf4\xfe\xa70\x04\x1e\xb0\xd7\xd1\xb8\ -Z \xcd\x10K+\xabloo\x9f\x18x\xe4N\xe2\ -:.\x1d\xcfG)\x852$\xa6\x12(\x83\xee\xda'\ -\xbaK\x8a\xaf\xe1\xfa\xb5<\xa6a\xe0\xe9.N\xd3\xbd\ -\x10\xe0\x0bp|\xe8\xf8 \x84\xa4\x5c.\xd3\xb6\xdbg\ -\x03\x94R\xe0{\x0e\xa0\x11\x02\x0c\xd9-\x02e\xf8\x88\ -\xfd\xf4\x0b\x01J)\xa4\x10\x88\xae\xe9\xf0\x9e\xdc\xcf\xa2\ -\xde_\xcb\xb5\xdf\xc1ul\xac\xc0\xc9\xb7\xbe#\x878\ -\x1c\x0e\xd3\x13\x10\xf8\x1d\x1b\xad\xbbo>\x1c\xc6\xfd\xe5\ -\xdd\x00~x\xf9\x86\x85\xa55\x0c\xb1\x8f\xa6\x9bB)\ -\xc0\x10`\xec\xff\xc0n\xd51\x84\xcf\xc0\xc0\xc0\xd9d\ -0\x1a\x8d\x91I\xc4x\xd7\xb4\xe95\xc1\x94\x02\xad\xc1\ -\x90\x92t\xdc\xc4\x0f\xe7\xdfA\xf1\ -\xd0\xadt\xc7\xd5\xd4\xb6Z\xac\xbc\x9a\x22\xda\x1f\xe4\xb3\ -_}~b\xdc\xb1@\x80\xc1\xc1ALCP^\x9e\ -ayq\x81\xca\x96\xc3\xf6\xae\x87\xeb\x81\xbf\x8f<\x80\ -~\x90D\x00lG\xb3\xb1i\xf3\xe6\xe5\x0c\x95\xd5\xe7\ -\xdc\xbf\xff\xf5\xa9\xdb\xaec\x1bV)%\xe3\xe3\xb7x\ -3?\xcf\xcc\xd3\x7f\xe0\xb96\x82[\x08\xd9C,l\ -\x10\xfc\xe0\x09\x02\xf0\x01\x0f\xd8\xb55oK-\x0aO\ -\xa7\x99\x9b\xfa'\xbf\xfb\xe2\x0b>\xff\xec\xd7\xa7\xc2\xc1\ -)\x1a\xd6\x9d\x9d\x1d\xfe\xf6\xf7\xbf\xf2\xfd\xd3\x02C\xf9\ -;\xdc\xb8s\x8f\xcb\xf94\xf1h\x90\xde\xa0\xc4\x94\x1c\ -\x0e\xe9\xd6N\x9b\xd5\xb5\xf7\xcc>\xf9\x8e\xc5\x97S\xfc\ -\xfe\xcb/\xf9\xfa\xfe\x1fQ\xea\xf4'\x8c\x13\x03\xa1\xdb\ -<<|\xf8\x80o\xbe\xfd\x86\xcd\x9d6C\x99k\xa4\ -sW\x18\x8c\x0f\xd3\x13\xb4p\x1d\x9b\xfaf\x85\xd2\xdb\ -E\xca\xeb\x8b\x84,\x83\x5c.\xcf\xbd\xbb\xbfab|\ -\x02q\xd0\x9a\x9d\x17\xf0 \xaa\xd5*\xcf\x9e\xcf\xf0\xe2\ -E\x81\x8d\x8d2{\xb6\x0dt\x8b\xaa\xb7'D&\x9d\ -fb\xfc\x16Z\x83\x15\xb4(\x95\x8a8\x8e\xc3\xf8\xcd\ -q\xc6\xc6>=\x7f\xe0Ax\x9eG\xa3\xd1\xa0\xd5j\ -\xe1y\x1e\xa6\xa9\xe8\xed\xed\xa3\xaf\xaf\x0f!\x04\xcb+\ -\xcb\x18\xd2\xa0\xb6Y\xe3\xc9\x93i\x84\x90\xfc\xf9O\x7f\ -!\x14\x0a\x9d\xf8\x1d\xc7V\xf1Qa\x18\x06\x91H\x84\ -T*E6\x9b%\x91H\x12\x0e\x87\xf7\xcf%\x1d^\ -\xbf~M\xab\xd5\x22\x9b\xc9\x90\xcb\xe5\xa9\xd7\xb7x\xf6\ -l\xe6T\xef\xf8E\xc0\xa3B)E6\x93evv\ -\x16)\x0d\xb2\xd9\x11\x92\xc9\x14\x8f\x9f>\xa6V\xab]\ -<\x10`ll\x0c\xcb\xb2x3?\xcfp|\x98l\ -6\x0bZ\xf3h\xea\xdf\x9ctf\x9d+P)\xc5\xe4\ -\xe4$\xcbKK\xb4\xf6Z\xa4\xd3\x192\x99,\xaf~\ -|\xc5\xca\xea\xca\xc5\x03\x012\x99\x0c\x89D\x82\xb9\xb9\ -9\x22\x97\x22d2\x19\xc2}a\x1e~\xf7\xe0D'\ -\xbcs\x07\x02LNNR\xab\xd5\xa8V\xab$\x93\xdd\ -\x82*\x16\x8b\xcc\xbe\xfc\xe1\xe3\x00F\x22\x11\xc6\xae\x8d\ -1\xf7j\x0e\xcb\xb2H\xa5\xd2\xc4\x87\xe2\x89D\x82t:M\ -\xa1\xf0\x9cb\xf1\xfd\xc5\x03\x01FGG\x89D\x22,\ -,,\x10\x89\x0c\x90J\xa5\xb0\xac S\xdfO\xa1\xb5\ -\x7f\xf1@)%w\xee\xdc\xa1T,\xd1h4\x88\x0f\ -\xc5I\xa5R\xd4jU\xf6\xf6\xf6.\x1e\x08\x10\x8f\xc7\ -\xb9r\xe5\x0a\xeb\xeb\xeb\x98\x81\x00===\x8c\x8c\xe4\ -\x08\xfc\x8f\x93\xde/j\x16~n8\x8eC\xb5V\xc5\ -\xf7}\x1a\x8d\x06\xe9T\x9a\xfe\xfe\xfe\x8f\x07x\x10\xbe\ -\xef\x1f\xdb#^(\xf0$\xf1\x1f\xb6J\xe0\xf90\x86\ -\x1f6\x00\x00\x00%tEXtdate:c\ -reate\x002023-03-08\ -T17:27:26+00:00\x9e\ -\x98\xa5\x85\x00\x00\x00%tEXtdate:\ -modify\x002023-03-0\ -8T17:27:26+00:00\ -\xef\xc5\x1d9\x00\x00\x00(tEXtdate\ -:timestamp\x002023-\ -03-09T06:18:41+0\ -0:00=\x00\x9f\x01\x00\x00\x00\x00IEND\ -\xaeB`\x82\ -\x00\x00\x03\xb2\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xa7\ -PLTE\xff\xff\xff\xfe\xfe\xfe\xf6\xf6\xf6\xdc\xde\xe0\ -\xca\xcd\xcf\xc6\xc8\xcb\xce\xcf\xd1\xe5\xe6\xe6\xfc\xfc\xfc\xe7\ -\xe8\xe9\xbc\xc2\xca\xc0\xc8\xd3\xd4\xda\xe3\xd7\xdd\xe6\xc8\xcf\ -\xd9\xae\xb5\xbf\xc2\xc5\xc7\xf6\xf6\xf5\xea\xeb\xeb\xb7\xbf\xcb\ -\xce\xd9\xe9\xe5\xef\xfa\xe9\xf2\xfc\xeb\xf4\xfd\xea\xf3\xfd\xdc\ -\xe6\xf4\xaf\xb9\xc7\xbe\xc1\xc3\xfb\xfb\xfb\xfb\xfb\xfa\xbf\xc5\ -\xcc\xbb\xcb\xe0\xdc\xea\xf9\xe6\xf2\xfc\xed\xf7\xfe\xd6\xe0\xe8\ -\xcd\xd8\xe1\xe4\xf0\xfb\xd2\xe0\xf2\xa8\xb2\xbf\xe8\xe8\xe8\xee\ -\xee\xed\xa1\xaf\xc4\xbb\xd2\xee\xde\xee\xfa\xdf\xeb\xf1\xb4\xc0\ -\xc9{\x8b\x9c\xa5\xb2\xbb\xee\xf9\xff\xe2\xee\xfb\xad\xbe\xd4\ -\xcf\xd1\xd4\xe4\xe4\xe3\x98\xab\xc4\xb9\xd4\xf2\xd0\xe2\xed\x96\ -\xa6\xb4\x86\x9a\xb0\x84\x98\xaf\xab\xb8\xc1\xf2\xfe\xff\xdf\xf0\ -\xfc\xa5\xba\xd6\xad\xb2\xb6\xff\xfe\xfe\xe7\xe7\xe6\x9c\xac\xc2\ -\xbd\xd7\xf2\xe2\xf3\xfa\xda\xe9\xee\xb9\xcb\xda\x8d\xa0\xb5\xab\ -\xb7\xc0\xf4\xff\xff\xde\xf1\xfc\x9f\xb4\xce\xab\xae\xb0\xbe\xc5\ -\xcf\xc2\xd7\xef\xe4\xf5\xfd\xf5\xff\xff\xf6\xfd\xfe\xd3\xde\xe4\ -\xcb\xd5\xda\xdc\xee\xfb\x91\xa0\xb2\xc9\xca\xc9\xdb\xdc\xdd\xa3\ -\xb4\xc8\xce\xe4\xf6\xf1\xfd\xff\xfa\xff\xff\xfe\xff\xff\xf1\xfc\ -\xff\xba\xcb\xdc\x98\x9e\xa2\xf4\xf3\xf3\xf9\xf9\xf8\xaf\xb3\xb5\ -\x90\xa0\xb1\xc5\xd8\xe7\xe8\xf4\xfb\xf0\xf9\xfd\xe4\xef\xf6\xb8\ -\xc5\xd1\x94\x9a\x9f\xe2\xe2\xe1\xf9\xf9\xf9\xc4\xc5\xc4\x96\x9d\ -\xa1\x96\xa0\xa8\x9d\xa6\xad\x9d\xa4\xa8\xa2\xa4\xa5\xb7\xb8\xb6\ -\xde\xdf\xde\xf8\xf8\xf7\xe9\xe9\xe8\xe6\xe6\xe5\xf3\xf3\xf2\xe5\ -\xe5\xe4\xd3\xd3\xd3\xaa\xab\xa9\xed\xed\xed\xcc\xcc\xcc\xc5\xc5\ -\xc4\xb6\xb7\xb6\xd5\xd5\xd5\xd2\xd2\xd1\xad\xae\xac\xd9\xda\xd9\ -\xf5\xf5\xf5\xc9\xc9\xc9\xca\xcb\xca\xb3\xb4\xb3\xca\xca\xca\xaf\ -\xb0\xae\xd5\xd6\xd4\xfa\xfa\xfa\xfd\xfd\xfd\xe1 B\xa9\x00\ -\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\x09\ -pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\ -\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\ -\x1f\xc9N\xa7\x00\x00\x00\xcfIDAT\x18\xd3c`\ -\x80\x01F\x06t\xc0\xc4\xcc\xc2\xca\xc6\xce\x81\x22\xc6\xc9\ -\xc5\xcd\xc3\xcb\xc7/ \x88$&$,\x22*&.\ -!)%-\x03\x17\x93\x95\x93WPTRVQU\ -S\xd7\x80\x0bjji\xeb\xe8\xea\xe9\x1b\x18\x1a\x19\x9b\ -\xc0\x05M\xcd\xcc-,\xad\xacml\xed\xec\x1d\x1ca\ -\x82N\xce.\xaen\xee\x1e\x9e^\xde>\xbe\x08\xf7\xf8\ -\xf9\x07\x04\x06\x05\x87\x04\x86\x86\x85#l\x8f\x88\x8c\x8a\ -\x8e\x89\x8d\x89\x8bOHD\x08&%\xa7\xa4\xa6\xa5g\ -dfe#;>'7/\xbf\xa0\xb0\xa8\xb8\x04\xd5\ -\xaf\xa5e\xe5\x15\x95U\xd5N\x18\x01PS[W\xcf\ -\x81!\xda\xd0\xd8\xd4\x8c!\xd8\xd2\xda\xd6\xc4\x84!Z\ -R\xdb\xde\x86!(\xd3\xd1\xd6\x89\xa9\xb4\xa6\xb9\xab\x1b\ -C\x90\xa1\x07\xe4R\x003\x9b+(8\xcb\x02\xf6\x00\ -\x00\x00%tEXtdate:crea\ -te\x002023-03-08T17\ -:27:27+00:008\xef\xae1\ -\x00\x00\x00%tEXtdate:mod\ -ify\x002023-03-08T1\ -7:27:27+00:00I\xb2\x16\ -\x8d\x00\x00\x00(tEXtdate:ti\ -mestamp\x002023-03-\ -09T06:20:50+00:0\ -0\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\xaeB`\ -\x82\ \x00\x00\x0bH\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -2762,136 +1158,6 @@ timestamp\x002023-0\ 3-09T06:18:41+00\ :00=\x00\x9f\x01\x00\x00\x00\x00IEND\xae\ B`\x82\ -\x00\x00\x03\xde\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xd1\ -PLTE\xff\xff\xff\xee\xf2\xf8\xdd\xe5\xf1\xf7\xf8\xfb\ -\xf5\xf7\xfb\xaf\xc3\xdd\xa4\xc0\xdf\x93\xae\xd3\xe4\xea\xf3\xf2\ -\xf4\xf8\xa1\xbb\xd8\xbc\xdb\xee\xa1\xc2\xdf\x84\xa4\xcd\xc6\xd3\ -\xe7\xfe\xfe\xff\x99\xb3\xd1\xad\xcc\xe2\xaf\xcf\xe4\xa6\xc5\xde\ -\x83\xa4\xcc\xa5\xba\xd9\xf2\xf5\xf9\x90\xab\xc9\xa1\xc0\xd9\xa2\ -\xc1\xda\xa1\xc0\xd8\x87\xa8\xcc\x8a\xa6\xcd\xda\xe3\xef\x87\xa2\ -\xc2\x96\xb6\xd2\x98\xb8\xd3\x98\xb7\xd2\x96\xb6\xd1\x96\xb5\xd0\ -\x89\xa9\xc9z\x9a\xc3\xba\xca\xe1\xfb\xfc\xfd\xf2\xf4\xf7\x82\ -\x9d\xbe\x8e\xae\xcd\x90\xb0\xcf\x8f\xaf\xce\x8d\xad\xcc\x8b\xaa\ -\xc9\x89\xa8\xc6\x83\xa2\xc1p\x92\xba\x98\xaf\xd0\xeb\xf0\xf7\ -\x7f\x9a\xbb\x89\xaa\xcd\x88\xa8\xcb\x86\xa6\xc9\x83\xa3\xc5\x7f\ -\x9f\xc1{\x9a\xbcw\x96\xb6h\x87\xadn\x8c\xb6\xcc\xd7\ -\xe8~\x98\xba\x82\xa4\xcb\x84\xa5\xcc\x82\xa3\xca~\x9f\xc7\ -x\x99\xc1p\x91\xb9e\x85\xaeTs\x9e>^\x8a#\ -Du2T\x87\xb1\xc1\xd9\xf2\xf4\xf6w\x92\xb6p\x94\ -\xc5o\x93\xc4k\x8f\xc1d\x89\xbc[\x80\xb4Pt\xa8\ -Cg\x9b7Z\x8e,M\x7f\x1f@r5U\x84\xb1\ -\xc1\xd8u\x91\xb5j\x90\xc6j\x8f\xc5g\x8c\xc2a\x86\ -\xbdY\x7f\xb6Qv\xacFj\x9f:]\x910S\x84\ -`z\xa1\xce\xd7\xe4\xf2\xf3\xf5y\x95\xb8t\x99\xces\ -\x97\xccn\x93\xc8^\x84\xbaTy\xb0Fk\xa0Cf\ -\x94\x8d\xa0\xba\xec\xef\xf4{\x99\xba|\xa1\xd5z\x9e\xd2\ -t\x98\xcdk\x90\xc6a\x87\xbdQx\xac\x5c{\xa1\xb8\ -\xc4\xd2\xfc\xfc\xfd\xf2\xf3\xf4|\x9c\xbc\x84\xa9\xdc\x81\xa4\ -\xd8y\x9d\xd1l\x92\xc7X\x80\xaf|\x95\xb0\xdc\xe1\xe7\ -\xf1\xf2\xf4}\x9f\xbd\x8b\xb0\xe3\x86\xa9\xddr\x9b\xcd_\ -\x86\xad\xa1\xb2\xc2\xf4\xf5\xf6\xf1\xf2\xf3}\xa1\xbf\x8d\xb4\ -\xe7o\x9b\xc9n\x90\xac\xc8\xd1\xd8\xf4\xf4\xf5x\xa0\xb8\ -a\x8f\xb4\x8a\xa1\xb3\xe8\xea\xed\xfe\xfe\xfe\xcd\xd9\xe0\xc6\ -\xcf\xd6\xfa\xfa\xfa\x8f\x8b'\x1a\x00\x00\x00\x01bKG\ -D\x00\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\x00\ -\x00`\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07t\ -IME\x07\xe7\x03\x09\x06\x142\x1f\xc9N\xa7\x00\x00\ -\x00\xd1IDAT\x18\xd3c`\xc0\x05\x18\x99\x981\ -\xc4XX\xd9\xd89\xd0\x059\xb9\xb8yx\xf9\xf8\xd1\ -\x04\x05\x04\x85\x84ED\xc5P\x05\xc5%$%\xa5\xa4\ -edQ\x04\xe5\xe4\x15\x14\x95\x94UT\xd5\xd4\x11\x82\ -\x1a\x9aZ\xda:\xbaz\xfa\x06\x86F\xc6\x08A\x13i\ -S3s\x0bK+k\x1b[;\xb8\xa0\xbd\x83\xa3\x93\ -\xb3\x8b\xab\x9b\xbb\x87\xa7\x977T\xd0\xc7\xd7\xcf? \ -0(8$4,<\x22\x12&\x18\x15\x1d\x13\x1b\x17\ -\x9f\x90\x98\x94\x9c\x92\x0a\xd3\x9e\x96\x9e\x91\x99\x15\x9b\x9d\ -\x93\x9b\x97_\x00\xb7(\xad\xb0\xa8\xb8\xa4\xb4\xac\xbc\xa2\ -\xb2\x0a\xe1\xa4\xea\x9a\xda\xba\xfa\x86\xc6\xa6fd\xc7\xb7\ -\xb4\xb6\xb5wtvu\xa3x\xb3\xa7\xb7\xaf\x7f\xc2D\ -\xb4P\x9a4y\xca\xd4i\xe8A7}\xc6\xccY\x0c\ -\x04\x01\x00\x16~0NH\xc6\xd0\xc2\x00\x00\x00%t\ -EXtdate:create\x002\ -023-03-08T17:27:\ -26+00:00\x9e\x98\xa5\x85\x00\x00\x00%\ -tEXtdate:modify\x00\ -2023-03-08T17:27\ -:26+00:00\xef\xc5\x1d9\x00\x00\x00\ -(tEXtdate:timest\ -amp\x002023-03-09T0\ -6:20:50+00:00\xdd\xe8\xb2\ -\xde\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x03\xf9\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x04\x00\x00\x00&\xf7p\xe6\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\ -bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\ -Ys\x00\x01bT\x00\x01bT\x01\x80\xc1\xe1l\x00\ -\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x12)\xc3\xf6\ - \xcd\x00\x00\x02\xc8IDATH\xc7\xad\xd7\xcdo\ -TU\x14\x00\xf0\xdf\xb43\xfd\x80*\xd0\xaa\xc1D\x89\ -JH\x91\xaa DK\xa3X\x87\x8e.\x5c41\xb8\ -5a\xa1\x0b\x12\x09\xd1\xb8!1\xb1,\x8c\x0d\x89$\ -.\x8cnH\x8c\x7f\x82\x1a]8\xa5i\xbbhT4\ -R\x81\xc8G\x95\x85DT\xa8\xd5\xb6R\xa6v\x9e\x8b\ -\xa9\xb6\xf3\xde+\x99;z\xcej\xce\xe4\xfe\xeey\xf7\ -\xce}\xef\x0d\xb4:`BI\xb4\x94eokVw\ -\xb4\x1at\xfd_\xac\x92\xf3\x064\xd5\xc7e\xedwH\ -\x8b\xef}\xeaW\xd0d\x9fN\x87e\xbc\xa1T\x0f9\ -!2\xa9{E\xa5\xc7Y\x91\x1b\x8e\xd4\xd3eFI\ -\xce;^\xaa\xaa\xf68\xee~%G\x1dW\x96I\x19\ -\x17\x99s-\x1d\x8c0\xe0H\xac^!\x17\x5cQ^\ -\xa5\x95i#\xde\xf5]\xda\x5c\x91\xd7S\x06\xf4\x98\x88\ -mU2OW-\xd5\xd2\xa6T\xfaL\xc6\xb8\xe7<\ -c}\xea\xe5\x92\xf3\xb0\x82.\x83\xf6\x99Jv8\x10\ -\xbe\xf8Z\xbd%R\xd2_]n\xa8\x83\xaa\xc4u\xef\ -\xbb*\xa7\xeb\xff\x02\x995O\xfcL\xfd\x17pQD\ -\xfcW\x90\xbd\xc9\x80\x0d\x1e\xf1\x90\xb6U\xbe-k\xb7\ -\x0e\xf9\xa5\xb6\x16M\x1a\xf2\xf3j\x9b\xd2\xe8Yc\x89\ -3~\xf3,\x1b\xb53\xbd\xc3\x9cW\xbc\xa6\x0d\xbf\x99\ -\x11\xd5\xb4\x00\xcdn\xb7\xc7\x9b\xe9\xe0\xf3\x06\xb4\xf8\xc9\ -{>N?`\x89\x88\xb4:\xaa\xdfci\xe0\xdd^\ -\xd5\xe2\x92\x17\x0c\x05m\xd2\x0c\x16\xd2vy\xaf\xad\x16\ -\x0d\x06r\x9bt\xe3d\x12l\xf4\xb4\x8c\x1f|\x12\xc4\ -\xf1\xa8{,\xfa0\x09\xdee7\xc6]\x0e\x04\x0b\xb2\ -.;\x91\x04\xbbmR\xf6\xd9\xaa\xb7\xad\xf4\xd8h\x0f\ -N\xba\x10\x073\x9e\x92\xf5\xa3\xf1\xc0\xfev\xd9\x8c\xa2\ -R\x1c\xbc\xd3\xe3\xf8\xc2\xa5\xe0\x0bn\xf6\x8b\xb1\xe4Y\ -\xde\xe5>\x14\xfd\x15\xc4ux\x02_\xbb\x98\x04\x0b\x9a\ -\x5c1\x16\xd8\xdfv\x9d(\x9a\x8f\x83\xb7\xe9\xc5W&\ -\x03\xc1>kM\x19!\x0e\xee\xb0\x05E7\x82\xb8u\ -\xf2\xf8\xb6\xf2\xc0\xaa\x06\xfb\xacq\xcdh`\x7f]\xba\ -0d6\x0e\xae\x97\xc7)\xe7\x02\xc1\xbc[\xcd\x18\xae\ -|X\x09>`\x1b\x86\xcc\x05qk\xed\xc5Y\xa7\x93\ -`\xde-~\xffg\xa6\x9a\xa3\xd3v\x0c\x9b\x8e\x83m\ -\xfap\xc6\x99@\xb0W\x87?\x97\xefL\xcb\xe0V\x0f\ -b\xd8\x1fA\x5c\xb3\x02\xce\xfb\xa6\x1a\x8c\xd0\xab\xdd\x9c\ -\x13\x81\xfdm\xb6\x13\xa3\xae\xc6;\xcc)\xe0\x9cS\x81\ -\xe0\x936*).\x17\x1a,\xa0\xc3\xbdv`\xa4\xc6\ -'\xc8r\x7f/\xe2\xbc/W\x16'D.:lV\ -d\x7f\x00\xd6h\xb7a\x91\xb2\x97W\x963\x0e8\xa6\ -\xc5\xac5\x1a|\xaeh!\xf5]\xac:\x22Y[\xe4\ -\xdd\x81\x0f\x1c\xac\xde\xc8\xb4\x97\xf6Zs\xda1\xed\xc9\ -\xf9\xe2\x7f+j\xcb)\x1f\xe9\x97\x8bc\x7f\x03\xa8M\ - \xd2E{(\x13\x00\x00\x00%tEXtda\ -te:create\x002023-0\ -3-08T17:21:49+00\ -:00\xa3\xa1\xac\xac\x00\x00\x00%tEXtd\ -ate:modify\x002023-\ -03-08T17:21:49+0\ -0:00\xd2\xfc\x14\x10\x00\x00\x00(tEXt\ -date:timestamp\x002\ -023-03-09T06:18:\ -41+00:00=\x00\x9f\x01\x00\x00\x00\x00\ -IEND\xaeB`\x82\ \x00\x00\x03\xac\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -2953,234 +1219,6 @@ EXtdate:timestam\ p\x002023-03-09T06:\ 20:50+00:00\xdd\xe8\xb2\xde\x00\ \x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x04e\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xc5\ -PLTE\xe1\xe1\xe1\xda\xda\xda\xdc\xdc\xdc\xdb\xdb\xdb\ -\xf8\xf8\xf8\xff\xff\xff\xe7\xe8\xe8\xeb\xeb\xeb\xeb\xec\xec\xec\ -\xed\xed\xed\xed\xed\xee\xee\xee\xee\xef\xef\xef\xef\xef\xef\xf0\ -\xf0\xf0\xf0\xf0\xf1\xf1\xf1\xdd\xdd\xdd\xf4\xf4\xf4\xed\xee\xee\ -\xf0\xf1\xf1\xf1\xf2\xf2\xf2\xf3\xf3\xf3\xf3\xf3\xf4\xf5\xf5\xf5\ -\xf5\xf5\xf6\xf6\xf7\xf6\xf6\xf6\xf6\xf7\xf7\xd8\xd8\xd8\xe2\xe2\ -\xe2\xe8\xe8\xe8\xec\xec\xec\xf2\xf2\xf2\xdd\xe9\xdc\xee\xf2\xed\ -\xfa\xfa\xfa\xe3\xe4\xe4\xea\xea\xea\xf7\xf7\xf7\xf9\xf9\xf9\xf4\ -\xf6\xf4\x90\xbf\x8b\x95\xc2\x8d\xdd\xea\xdc\xe0\xdf\xe0\xf5\xf6\ -\xf6\xf4\xf7\xf5\x90\xbf\x88\xaa\xd2\x89\x8c\xbe{\xaa\xc3\xa9\ -\xec\xed\xec\xf9\xf8\xf9\xf8\xf9\xf9\xfb\xfb\xfb\xf5\xf8\xf6\x88\ -\xb9\x81\xad\xd3}\xae\xd4y\x83\xb8c\xa0\xc6\x9a\xeb\xf1\ -\xec\xfc\xfc\xfc\xf6\xf8\xf7}\xafs\x95\xc5P\xa3\xcfU\ -\xa1\xceT\x83\xb9I\x85\xb5q\xdc\xe7\xdc\xf7\xf6\xf7\xfd\ -\xfd\xfd\xf7\xf9\xf9r\xa4_\x81\xbd\x12\x95\xcd\x19\x93\xcb\ -\x1az\xb5\x17k\xa1G\xcc\xdd\xcd\xf8\xf7\xf8\xfe\xfe\xfe\ -r\xa0Z\x93\xce\x09\xaa\xe2\x0b\x81\xba\x0fy\xa6\x5c\xd4\ -\xe1\xd5\xde\xde\xde\xf9\xfa\xfa\xf7\xf9\xfaq\x9bV\x90\xc8\ -\x06{\xaf\x1f\x82\xa1w\xe5\xe9\xe7\xf3\xf4\xf5a\x87S\ -X\x850\xaa\xbc\xac\xd9\xdb\xdd\x8e\x9f\x92\xa9\xb2\xac\xd3\ -\xd4\xd6\xd8\xda\xdb\xde\xdf\xdf\xe0\xe0\xe0\xc5\xc6\xc6\xcf\xcf\ -\xd0\xcf\xcf\xcf\xd1\xd2\xd1\xdc\xdd\xdd\xc5\xc5\xc6\xe5\xe5\xe4\ -\xd6\xd6\xd5\xdf\xdf\xdf\xd0\xd0\xd1\xd2\xd2\xd2\xd3\xd5\xd6\xe1\ -\xe2\xe3\xe4\xe4\xe4\xbc\xbc\xbd\xce\xce\xce\xd8\xd8\xd7\xf6\xf6\ -\xf5\xd4\xd4\xd5\xcc\xcd\xcd\xc8\xc9\xc9\xdd\xdd\xde\xfa\xfb\xfb\ -\xe2\xe3\xe4\xc3\xc6\xc7\xd7\xd8\xd7\xe0\xe1\xe1\xe3\xe4\xe3\xd9\ -\xda\xd9\xdf\xe0\xdf\xe5\xe5\xe5\xe2\xe2\xe3\xd7\xda\xdb\xda\xdc\ -\xdd\xbc\xbd\xbd\xd5\xd5\xd4\xd1\xd3\xd4\xc5\xc7\xc7\xd3\xd3\xd3\ -\xdf\xe0\xe0\xd9\xda\xdb\xca\xce\xd0\xfc\xfaE\x9c\x00\x00\x00\ -\x01bKGD\x05\xf8o\xe9\xc7\x00\x00\x00\x09pH\ -Ys\x00\x00\x00`\x00\x00\x00`\x00\xf0kB\xcf\x00\ -\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\x1f\xc9\ -N\xa7\x00\x00\x01dIDAT\x18\xd3M\xd0gS\ -\xc2@\x10\x06\xe0\xe3\xe4\xc4\x02\x18N$\x86(\x1a\x8d\ -&\xf6\x0e\xf6\xd85v={\xef\x15\xb1b\xc3.\xf6\ -^\x7f\xafw\x8c\xce\xf8~|fwv\xf6\x05\xc0\x00\ -\xff%\xcc\x88h\x80!\xdc\x14\x11\x19\x15m\xb6X\xac\ -11\x9c\x0d\x87\x10\x9ab-\xf68\x07\x8f\xe3\x05\xa7\ -\x98\x90\x98\xe8r\x81$\x00\x93\xadv\x89\xc7\x82\xe8\x14\ -SRe\x98f\x8eN7\x00\x18\xcb9()F5\ -#3+;\xc7$\xe6r\x10@3\x8fs)\xc9r\ -^~AaQq\x89\xc2S\xb4\xc6\x8b\x8a[\x96=\ -\x9e\xd2\xb2\xf2\x8a\xca\xaaj\x0f\xb6\x01h\x17\x15\x95\x92\ -\xa6\xd5\xd4\xd6\xd57465\xb7P\xe4~Mom\ -k\xef\xe8\xec\xea\xee\xe9\xa5(\xb9\x99\xe9:i\xed\xeb\ -\x1f\x18\x1c\x1a\xd6\x95\x11\x00\xf9Q9dhl|b\ -rj\x1a\x11#E\xfckdfvn~\x81~\xa4\ -2d\xcb\x84\xe8\x82kqiyE&\xc4\xbd\xcaP\ -c\xa8x\xd7|\xeb\x1bX\xc5\xbc}\x13@\x81\x0d\x22\ --j\xcb\xb7\xbd\xc3K\xfe\xdd\xbd\xfd\x83?\xf4\x1c\x06\ -\x8e\x8eO\xa4\xd3\xad\xb3\xf3\x0b\x8d\x22=\x83\xd0e\xf0\ -\xeaZUo\x02\xb7w\xf7:\x026\xf0\xf0\x00\x0c\x87\ -\x8fO\xcf*\x12\xf6^^\x01\xa1}\xca\x8abL\x0e\ -\xc3X\xd4\x10\xc2o\xef\xdb\x84\x95\xcc\x9a\x16>\xfc~\ -\xaf\xd7\xfb\xf9\xf5\x1dd\x86~\x00\xa3\xa7N\xa9\x0f0\ -\xa84\x00\x00\x00%tEXtdate:c\ -reate\x002023-03-08\ -T17:27:26+00:00\x9e\ -\x98\xa5\x85\x00\x00\x00%tEXtdate:\ -modify\x002023-03-0\ -8T17:27:26+00:00\ -\xef\xc5\x1d9\x00\x00\x00(tEXtdate\ -:timestamp\x002023-\ -03-09T06:20:50+0\ -0:00\xdd\xe8\xb2\xde\x00\x00\x00\x00IEND\ -\xaeB`\x82\ -\x00\x00\x02\xa6\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x04\x00\x00\x00'\x80\xd5\x86\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\ -bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\ -Ys\x00\x00\xb1*\x00\x00\xb1*\x01a\xa7,\xef\x00\ -\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x142\x1f\xc9\ -N\xa7\x00\x00\x01uIDAT(\xcf\x85\xd3\xb1k\ -SQ\x14\x06\xf0\xdf}}\xa1i2\x95b\x0dH2\ -i\xe8\xe0\xe4$os\xb4\x9bm\xf1\x1f\xd0\xa5E\xf0\ -\x1f\xa8\x93nBA\x10]\xc4\xbf@p\x15\xba:\x08\ -B!S\x87\x84.E\xb0\x95R\xb0$\xc6$}\xcf\ -\xe1\xbd$M\x11\xf2\xdd\xe5\x9es>\xee9\xf7\xbb\xdf\ -e\x8aD\xcb\xc0\xdfb\x0d\xb4$\xd3b\x98\xec\xaa>\ -)y\xa7/ S\xb6chK\xd754tl\xce\ -d6u4\xc6A4IgR\xbd\x19bO*\x1b\ -\x071\xaaVd\xeaJjn\x89\x8b\xcaHMI\x1d\ -\xc1\x99n\x90\xd8\xd5\x94\x8a\xddt\xae7\x99:S\xb1\ -\xec\xc4H\xa4\xede\xd0\xf2\xcb{}\xa4W\x06\xc9\x91\ -g\xca\xb6\xdd``\xc3\x10T<\xf4S\ -\xc5\xb6\xc3 \xf1\xc2\x1d\xa9\x92U\x8f\xacxU\x9c\xb7\ -\xeb\xccg\xa7\x86\xb9\xe0\xb9o\x1a\xea\x12G\xd6-j\ -Z\xb3\xa6i\xd1\xba#\x89\xba\x86j~\x99na\xa5\ -\x91\xd4\x03\xcf\x8b\xd6o\xa4F\x8e\x1dOM1\x16#\ -\xb2\xa4\xed\xbb\x08\xa9\x1fn\x8b\xa6~\xbdn\xdc\xb7\xfe\ -\x14\xf1\x92g\xff7\xee\x9c\xaf\xf0\x0fs(t\xfdd\ -$_B\x00\x00\x00%tEXtdate:\ -create\x002023-03-0\ -8T17:21:49+00:00\ -\xa3\xa1\xac\xac\x00\x00\x00%tEXtdate\ -:modify\x002023-03-\ -08T17:21:49+00:0\ -0\xd2\xfc\x14\x10\x00\x00\x00(tEXtdat\ -e:timestamp\x002023\ --03-09T06:20:50+\ -00:00\xdd\xe8\xb2\xde\x00\x00\x00\x00IEN\ -D\xaeB`\x82\ -\x00\x00\x06\xbc\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x03\x00\x00\x00\xbb H_\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x02\xb2\ -PLTE\xff\xff\xff\xf3\xf3\xf3\xe9\xe9\xe9\xfd\xfd\xfd\ -\xc1\xc1\xc1\xc7\xc7\xc7\xd0\xd0\xd0\xd1\xd1\xd1\xfc\xfc\xfc\xbc\ -\xbc\xbc\xeb\xec\xec\xf2\xf2\xf3\xf0\xf1\xf1\xf1\xf1\xf2\xf1\xf2\ -\xf2\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf4\xf4\xf4\xf5\xf5\xf5\xf5\xf5\ -\xf5\xf6\xf6\xf6\xf7\xf7\xf7\xf7\xf7\xf8\xf9\xf9\xed\xed\xed\xbd\ -\xbd\xbd\xe8\xe9\xe9\xe8\xe9\xea\xe7\xe8\xe8\xe9\xea\xea\xea\xeb\ -\xea\xeb\xec\xeb\xec\xed\xed\xed\xee\xee\xee\xee\xee\xe5\xe6\xe6\ -\xe0\xe0\xe0\xe0\xe1\xe1\xf1\xf1\xf1\xf2\xf2\xf2\xeb\xeb\xeb\xea\ -\xea\xeb\xe9\xea\xe9\xec\xec\xea\xea\xeb\xeb\xda\xe2\xec\xdb\xe3\ -\xed\xef\xef\xed\xef\xef\xee\xf0\xf0\xee\xf0\xf0\xef\xf1\xf1\xef\ -\xae\xae\xae\x9a\x9a\x9a\xa3\xa3\xa3\x97\x97\x97\xf5\xf4\xf4\xf3\ -\xf3\xf2\xf4\xf4\xf2\xf4\xf4\xf3\xf5\xf4\xf3\xdf\xe7\xf3\xe4\xea\ -\xf3\xf6\xf5\xf3\xbe\xbe\xbe\xd6\xdf\xeb\xa4\xc3\xedu\xa8\xef\ -[\x99\xf1\x90\xb8\xf0\xb0\xcb\xf0\xb4\xce\xf1\xb9\xd1\xf2\xbd\ -\xd3\xf2\xc1\xd6\xf3\xc5\xd8\xf2\x9e\xa4\xad\xce\xce\xce\xc0\xc0\ -\xc0\xa9\xaf\xb8\xd4\xe3\xf8\xcd\xde\xf6\xc7\xdb\xf5\xc2\xd7\xf5\ -\xbc\xd4\xf4\xb6\xd0\xf4\x8a\xb6\xf3Z\x9a\xf2{\xac\xf2\xb0\ -\xcc\xf3\xe7\xed\xf5\xec\xec\xec\xd2\xdd\xec\x90\xb7\xee]\x9a\ -\xf0K\x90\xf2\x84\xb1\xf1\x9e\xc1\xf2\xa3\xc4\xf2\xa9\xc8\xf3\ -\xaf\xcc\xf4\xb5\xcf\xf4\xba\xd2\xf4\x9b\xa4\xaf\xd4\xd4\xd3\xc4\ -\xc4\xc4\xa5\xae\xb9\xcc\xdf\xf9\xc3\xd9\xf7\xbb\xd4\xf7\xb4\xcf\ -\xf6\xac\xcb\xf5\xa5\xc7\xf5~\xaf\xf4I\x8f\xf3a\x9d\xf2\ -\x9a\xbe\xf3\xe3\xeb\xf6\xbe\xbd\xbd\xed\xed\xec\xee\xee\xed\xe6\ -\xea\xee\xcf\xdd\xef\xd8\xe2\xf0\xf1\xf1\xf0\xf2\xf2\xf0\xf2\xf2\ -\xf1\xf3\xf3\xf1\xac\xac\xab\x9f\xa0\xa0\xad\xad\xad\xbb\xbb\xbb\ -\xf8\xf8\xf7\xf6\xf6\xf5\xf7\xf7\xf5\xf7\xf7\xf6\xf8\xf7\xf6\xd7\ -\xe4\xf6\xd8\xe5\xf6\xf0\xf3\xf6\xf8\xf8\xf6\xf8\xf8\xf8\xea\xea\ -\xea\xee\xef\xef\xf0\xf1\xf0\xe1\xe2\xe2\xd6\xd6\xd6\xd7\xd7\xd7\ -\xe7\xe7\xe7\xf6\xf6\xf6\xf9\xf8\xf7\xef\xef\xef\xef\xf0\xf0\xf7\ -\xf8\xf8\xf9\xfa\xfa\xf9\xf9\xf9\xfa\xfb\xfb\xf0\xf0\xf0\xfa\xfa\ -\xfa\xfb\xfb\xfb\xfe\xfe\xfe\xbc\xbc\xbd\xe5\xe7\xe8\xbb\xbc\xbc\ -\xe8\xe8\xe8\xe4\xe4\xe4\xe2\xe2\xe2\xe3\xe3\xe3\xe8\xe8\xe9\xd7\ -\xdb\xdd\xba\xba\xbb\xfd\xfd\xfc\xd9\xda\xda\xd1\xd1\xd2\xcb\xcb\ -\xcc\xc8\xc8\xc9\xcc\xcd\xce\xd2\xd5\xd7\xb3\xb6\xb7\xd5\xd5\xd5\ -\xde\xde\xde\xbf\xc0\xc1\xb5\xb6\xb7\xb4\xb4\xb5\xc0\xc1\xc1\xcd\ -\xce\xce\xb6\xb7\xb7\xc7\xc8\xc8\xb4\xb5\xb5\xb7\xb8\xb8\xc3\xc4\ -\xc5\xdd\xdd\xdc\xbc\xbd\xbb\xd2\xd3\xd2\xce\xce\xcf\xb3\xb4\xb5\ -\xcc\xcd\xcd\xe6\xe6\xe5\xbe\xbf\xbd\xd4\xd4\xd4\xd7\xd7\xd8\xc0\ -\xc0\xc1\xa9\xaa\xab\xdd\xdd\xde\xe5\xe5\xe4\xd5\xd5\xd4\xe5\xe5\ -\xe6\xb4\xb5\xb6\xac\xad\xae\xe1\xe1\xe1\xae\xae\xaf\xc1\xc2\xc2\ -\xe3\xe4\xe3\xbf\xbf\xbe\xd6\xd6\xd5\xde\xde\xdf\xc4\xc4\xc5\xdf\ -\xdf\xdf\xc6\xc7\xc8\xc1\xc4\xc6\xba\xba\xba\xfb\xfc\xfc\xe3\xe5\ -\xe6\xca\xd0\xd3\xc6\xc6\xc6\xc4\xc5\xc5\xbf\xc2\xc3\xb9\xbd\xbf\ -\xad\xb1\xb3\xd4\xd5\xd5\xe4\xab\x9e\xc5\x00\x00\x00\x01bK\ -GD\x00\x88\x05\x1dH\x00\x00\x00\x09pHYs\x00\ -\x00\x00`\x00\x00\x00`\x00\xf0kB\xcf\x00\x00\x00\x07\ -tIME\x07\xe7\x03\x09\x06\x12)\xc3\xf6 \xcd\x00\ -\x00\x02\xceIDAT8\xcb\x95\xd4\x87S\xd3`\x18\ -\x06\xf0\xa4\xa1XjMGh\xda\xe2j+\xa6UQ\ -\x11EE\x1c\xb8'\xee]\xf7\xde{\x0f\x5c(\xae\x8a\ -\xa2Qj\xa5\xad\x03q\xe0\x02\x9c8Apoq\x0f\ -\x5c\xff\x87\xdf\x974\xc9\x97\xb6z\xfa\xdc\xf5z\x97\xfc\ -\xeey\xf3~\xcd\x15\xc3\xfe'\xb8\xe2\xaf\xc1\x05GD\ -)\xa3\xff\x9c\x1a\xca(\x22\x08U1\xea\x9a\x9aZ\xa4\ -V\x8b\xeb\xf4\x06\x03\x15k4\xd2F\x10\x9a\x8f\xc9\x1c\ -\xa3\x12\xa0%\xaev\x9d\xb8\xba\xf5\xea\xab\xadV\x9b\xcd\ -\xde >\xbe\xa1B\xa3a\x18\x92t8H\x12wZ\ -$\xa8h\xd4\xb8IB\xd3f\xcd\x13[$\xb5Ll\ -\x95\xdc\xba\x8d\xa5mJ\xbb\xd4\xf6\xa9\x1d:\xe2\x9d(\ -\x04\xa6)\xd4\x9d\xbbt\xed\xd6\xbdG\xcf^\xbd\xfb\xf4\ -\xed\x97n\xeb?`\xe0\xa0\xc1C\x86\x0e\x1b>b\xe4\ -\xa8\xd1.\x14Z\xc7\x8c\x1d7~\xc2\xc4I\x93\xa7L\ -\x9d6}\x065s\xd6\xec9s\xe7\xcd_\xb0p\xd1\ -\xe2%K\x11\xb8\xac\xaem\xf9\x8a\x95\xabV\xafY\x9b\ -\xb1.%c\xfd\x86\x8d\xc9\x9b27o\xc9\xda\x9a\xb5\ -m\xfb\x8e\x9dn\x04Zv\xd9\xad\xb6\xec\xa4\xdd\x1a\x86\ -th\xf1=99{\xf7\xb1,\xdc:s\x7f&m\ -\x92\xc1l[\xf6\x81\x5c\x0d\xe3\x80'\xc4\xd2\xb4\x1b \ -\x8f\x9b\xcfA\x19\xcc\x85\x0e\xd4\x81\x83\xa4bYN\x99\ -\xbc|\x0eYQ\x98\xc79P\x07\x98\x91\xe6\x94/\x18\ -\xbf\x19\x85\x1a\xc1\xb1,h\x13\x94\x1fF\x85\xc2\x04\x92\ -s\x06\x8a\xab\xf3\x8a(\x0c:\x1d\x0c\xef\xc0\x1e\x5c\x9d\ -\xa8@dPKj\xe1\x5c\xe8\xc4\x91\xc1\x102\xa8\x0b\ -u\x22\x0b\x81z\x5c\xcf=_\xd0\xf1\x86O@\x06\x0d\ -:\x038>\xe8\x84:\x82\x88\x08)88\xe8D\x16\ -\xe0\x82\xd9Q\x18\x0b\x06{x(\xb8@ \x02T\xb3\ -\xb2B\x81\xf1\xb7Q\xe8b#8p'\xac\xd1e\x14\ -\xa0\xe8\x80\x22T\xdcf\xea\xc3\xd2\x1b\xee\xa2\x8d\x1e\xa1\ -Pp\x84\xcaGS8\x8eSG\x8e\x22\xd0\x83\x16\xf2\ -\xceG;\xcc\xc7\xf2\x8f\x17\x9c8y\xea\xb4\x04\xdd\x10\ -J\x85\xd0Q\xf6\xfc\xc23g\xcf\x9d\xbfPT\x8c!\ -\xd0#/$|zWI\xfa\xc5K\x97\xaf\x5c\xbd\x16\ -\xadB\xa10\x99/\x0c\xf8YsI\xe9\xf5\x1b7\x0b\ -o\xdd\xbe#\xfc\xa1p\xd0$M\xe6\x0a\xbdLAY\ -\xf9]\xda]QyOr\x10zQ\x08\xae\xb0\xce\xfb\ -\x0f\x1e>\xf2?\xae|\x828\x09\x06\x1f\x11L\xc6\x9f\ -\x96={\xee\xac\xa8,F\x1d?\xda\x87B\x1f\xf3\xa2\ -\xf4\xe5\xab\xd7Uod\x8eoDw!\xbcyo\xdf\ -\x95GW\xe5\xc8\x9d\xb4\x8c\xb8\xb4\x97y\xff\xe1\xe3\xa7\ -P\x07~k;\xa1\x12_\x04\x98\xcf\xba/_\x8b\x8a\ -C\x1dF\xc4\xa4)\xab\xab\xab\x95J%\xfc\xc0\xafo\ -\xdf\x7f\xfc\xfc\x15\xe60\xcc\x1c\x1f\x1eE\x04\xf7O\xf9\ -\x0d]\x11i{\xdc\x1a\xbd\x00\x00\x00\x00%tEX\ -tdate:create\x00202\ -3-03-08T17:27:26\ -+00:00\x9e\x98\xa5\x85\x00\x00\x00%tE\ -Xtdate:modify\x0020\ -23-03-08T17:27:2\ -6+00:00\xef\xc5\x1d9\x00\x00\x00(t\ -EXtdate:timestam\ -p\x002023-03-09T06:\ -18:41+00:00=\x00\x9f\x01\x00\ -\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x07\x8d\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -3399,364 +1437,6 @@ e:timestamp\x002023\ tSoftware\x00Adobe \ ImageReadyq\xc9e<\x00\x00\ \x00\x00IEND\xaeB`\x82\ -\x00\x00\x16:\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed}[s[9\x92\ -\xe6\xfb\xfc\x0a\xad\xfa\xa5\x1dCB\x99\x89\xbc\xc1U\xee\ -\x89YW\xf4DGT\xc7l\xect\xc7\xee[\x07-\ -\x1e\xd9\xda\x96E\x05I\x95\xad\xfa\xf5\x0b\xc0$\x0f!\ -\xd2\xa4(SV\xd9S\x94\xabx\x90H\x5c\xf2C^\ -\x80\xc3\x03\x9c\x1f\xff\xed\xe3\xfb\xab\x93_\xba\xe9\xecr\ -r\xfd\xea\x14\x03\x9c\x9et\xd7\xe7\x93\xf1\xe5\xf5\xdbW\ -\xa7\x7f\xff\xdb\x9f\x87~z2\x9b\x8f\xae\xc7\xa3\xab\xc9\ -u\xf7\xea\xf4zr\xfao\x7f\xfa\x97\x1f\xff\xc7px\ -\xf2z\xda\x8d\xe6\xdd\xf8\xe4\xc3\xe5\xfc\xdd\xc9_\xae\xff\ -9;\x1f\xddt'\x7f|7\x9f\xdf\xbc<;\xfb\xf0\ -\xe1C\xb8\x5c\x10\xc3d\xfa\xf6\xec\xc5\xc9p\x98K\xce\ -~y\xfb/'''\xb9\xd9\xeb\xd9\xcb\xf1\xf9\xab\xd3\ -\x05\xff\xcd\xed\xf4\xaa\xf2\x8d\xcf\xcf\xba\xab\xee}w=\ -\x9f\x9da\xc0\xb3\xd3\x9e\xfd\xbcg?/\x8d_\xfe\xd2\ -\x9dO\xde\xbf\x9f\x5c\xcfj\xc9\xeb\xd9\x1f\xd6\x98\xa7\xe3\ -\x8b\xcc\xddw\xe6C\xacL\x98R:\x03:#\x1af\ -\x8e\xe1\xec\xeez>\xfa8l\x8b\xe6>n+J\x00\ -p\x96\xf3z\xce\x87q\xbd\xfcx\x95\x91\xf8lgj\ -\xeez\xeb\x19\xfd\x9b\xfc\xdf\xaa\xc0\x92\x10f\x93\xdb\xe9\ -yw\x91Kv\xe1\xba\x9b\x9f\xfd\xf4\xb7\x9fV\x99C\ -\x08\xe3\xf9x\xad\x9a%\xf8M\xbb\xcd\x88\x5c\x8f\xdew\ -\xb3\x9b\xd1y7;[\xd2K\xf9<\xa0\xe3\xf9\xbb\xac\ -\x0c\xe45\xf9\xae\xbb|\xfbn\xde\xa7/\xc7\xafN\xb3\ -|\xa4\xa05\xbd\xec\xc1\xcb\x95\x1aA\x88T\xb3\x96\xd5\ -\xaeg\xb1\xfc\xeb\xb8\xfb\xa5\xbb*\x0c\x8d\xe6\xb5\x95\x8d\ -'\xe7\xb9\x7f\xb9\xf7o'\xc3\x9bi\xf7\xcb\xe5\xe4v\ -\x16r\xb3\xbf\xb6\x15On\xe77\xb7\xf3\x7ft\x1f\xe7\ -\xdd\xf5\xa7\x9a\xb2dkb\xd6\xecZnE\xdch\xe7\ -\xcdh\x96\xdb9{7y\xdf\x9d\xdd\x5c^gy\xa7\ -\x93\xb3\xd1t\xfea2\xfd\xe7\xd9\x7f~\xbc{\xdb]\ -\x9f\xcd\xce\xa7\x97ong\xf9{t5zs\xd5\xb5\ -\xbd\xe8>\xdeL\xa6\xf3\xe1\xc5\xe5U\x97;\xbdQY\ -\xbe8\x9f\x9dMjM\xab\x1a\xceF\xe7\xf3\xdc\xe1\xd9\ -Y\x16\xf0:\xf7?\xdc\x5c\xbf\xddZ\xeb\xc7\xf1M\xd6\ -\x05t\xd8\x9a{\xb7\xca\xfdS\xce\xfeq\xdc]\xcc\xf2\ -\xf7b\x98J\x8a\x14\xbc\xe6\xe5\xdc\xaci\xddh\xfa\x1f\ -\xd3\xd1\xf82\xdbW\xa5\xadWy>\xb9\xba\xea\xce\xf3\ -P\x8f\xae>\x8c\xeef\xa5\xbdUUm\xd1\xc8\x9cj\ -\xa5\xb5\xda\xd9|r\xb3\xb8.\xd0\xce\xef\xae2\x04\x85\ -8\xcc5N\xa6/\xff\x00\xf5\xf3C%M\xb2\xc6]\ -\xce\xef^\xe2\x0f\xa7}\x99\xc9\xc5\xc5\xac\xcb\x0dW\x19\ -\xfbVk%\x91\x05OO\xce\xbe\xac5\xd8\xd6\x1an\ -o-\xaeZ\xfb\xf1\xac\x15{7\x8c\x9b(Q\x92\x03\ -P\xd2\xd8\xa5\x8b\x8b\xc7\xa2D\xc9\x0eB\xe9\xa2~\x1e\ -\x8b\x12\xa5\xb4\x03\xa5#+\x1b)\x9c\x1e(\xd8\xe3a\ -T\xfa\x9a0*\x1fO\xd9\x80\x0e@\xe9|\xdcu_\ -\x80\x12\xf0A(\x8d\xa9\xf3n\xfch\x94@\xf7\xa1T\ -R\xa3\xabm(\xfd\xdf\xbf\xfe\xfc\x97\x9f\xfe\xc1\xffX\ -U|\xfe\xf1\xd5){\x9f\xbc{u:\x84@\xb8F\ -\x9b\xbe:\x15\x09\xeb\x94\xb7\x8b\x9a\xff6\x1d]\xcfr\ -\x04~\xff\xea\xf4\xfdh>\xbd\xfc\xf8G\x08\xc9\x12\x0d\ - \xff\x95K\x92\x01\xc6\x18\x00\x80\x06\x04\xc1M\xe9\xc5\ -F-\x7f\xbf\xbe\x9c\xe7\xc9\xc3\xed\xac\x9b\xfeW\x09\xc0\ -\xffy\xfd\xf7Y\xd7\x0e\xde\xb6\x81\xd8\x09\xb1\xd1O\x18\ -\x7f\xda\x06\x1fxQ\xe9}\xb5\x87(\x12w7\x11\xe5\ -\xdf_\xe3\xeb\xedM\xf0C\x9aPL\xbc\xbb\x09\xf8s\ -\x12\xf0\xedM\xe8C\x9a0\xb1}M\x80\xbf\x06\xd8\xde\ -\x84\xefo\x02\xf7\xd5n\xff\xfe\x99\xda\x13\xaciq\xa3\ -\xb0\xad\x16\x1f\xec2\xeb\x0c\xf2\xe5\xbbi\x97g\xbc\x7f\ -h\xf4}\xd9\x81\xb6\xe2\xa8\x94\x1e\xa8\x93\xfb\xf5\x1f\xab\ -\xeac\xc0\xe81Q\xca\xd7C\x84 h.\xf6\xa21\ -:4j\xac\xcet\x95\xbc\xb8\x97}Q\xb3\x1b{,\ -\xa3\x7fD\x98Z/\xa2\xf9\xb3\x0b0\xc2\xf8\x1b\x04\xcc\ -=\xb8\xa3\xc5\x066$\x0c\xcc\x84q\x03\xbd&\xa0\x1c\ -\x13\xbdH\x0a\xbb\xe2w\x04\xec\xab\xc1,G\x0c\xc6\xa4\ -\xb1\xb7\xf3\xbbLE\x091\xaa0\xf7\xbc\x94y=\xa4\ -\x98bL=o\xa6\xa2\x06\x1309T\x8dgy)\ -1\x1a\xff\xb5\x9b\xbf\x9b\xe4nfY\x8a\xa8\xbb\xc6l\ -^.\xaf\xf2:\xf7\x8f<\x80\x17=\x88y\xaa?\xef\ -\xa6\x0f\x07\xaf\xc6\x18\x04\x02\x03\x94\x05\xb9_i\x05\x02\ -F\x86\xd8\xcb\xf8\x89\x9f8\x89\xf6\xe0\xf5K\xb1\x90s\ -\x12R\x03\xfa\xa7.\xc5\xe8\xd6G\x92\x8b\xee?F\xb7\ -\xb3\xd9\xe5\xe8\xfa\x7f^\xdd\x96\xee\xee\xe9p\xe3\xdb\xc6\ -?\xe5e\xd7h\xbe\x5c\xb7\xb1\x8ah\xba\x1f\xa0\xdb\x16\ -b\xf4\xf5y\xe1\xa7.=\xa1\xde%\xd9\xa9w)\x1e\ -U?\xaa\xeer0i\xf5\x96\x03\x99\xb8\x1f\xa4\xb7O\ -i\x8e\xca\xc0\xbb`a8\xd0l\xaa\xe0\x988\xdc\x13\ -\x9cbH\xa0\x1e\x1b\xc1\x118\x904RK@\x91$\ -N\x0f\xf6\x8e\x10\x5c\x11Xc\xf1\x8d\x9e\xff\x178*\ -\xba\xda\x17\x9b \x8aE\x88\xbc\xc5\x04#\xb2\x82m\x98\ -\xa0IL\xa2\xdbLP\x04,\xe16\x1bd\x8eOb\ -\x83\x91\x84\x13\x8a\xef\xb6Af\xd9\xb0\xc1'\x8b\x9c\x91\ -9\xed\x8a\x9c,\xd2\xc44\xa3\x00\x04\x8e\xb1\x89l\xaa\ -\xa1t\x1bz\xde\x8b\xad\xbc\x17[y\xa7\x99\x08\x0f\x9a\ -\xaa\x8b\xa3\x92\xcb\x00\x83\xa9\x99Z7\xa4\xc1\x90\x82&\ -W\xe2TR\x10\xdc\xcd\x93\xd3 bH\x84\xce:\x90\ -\x90\x18\x13$~\xe0L\xfeic-\xd0.\xe3\x16\xc2\ -\x03\x8c\xfbp[\x0c\xa8\x18\x1b\x8b<\xc0y\x12\xc5\x10\ -\xc9\x90\x1a?\x221\x983J\xebG\x884\xa8\xb4\xee\ -S,\x90%\x8a\x94\x11~\xae\xa8BD\xfa\x18\xf7i\ -)\xa0\x11\xf1\x86\x0b5q\xe5\xd6\x85:\x04\x06\xe3V\ -x\xa5\x90$%M\xcf+\xbc?\xb5\xf0\x16Cr0\ -l\x85\x97\xc0\xec\x88\xf4\xb0\xc9Z\xb2<[{N3\ -$\x89\x0d\x02l!\xa9\xb3Q\x8b\x80\x06\x8c.h\x0d\ -\x02\x9c\x02a\xcb\x9b\xa9.\x81\xc9\xe4\x90)\xef~\x9c\ -\x86\x10d0t\x0b\xeaHL/\x9es\x95 \x11\xfe\ -{x\xaeg\x8b\xc5\x12\xf9h\x08C qU\xb0A\ -\xd1!Bb\xe3\x028\x19G.W\x8c\xa0\x808\x18\ -F\x09\x12\x13\xcb@ \x80\x88$iW\xb9C\xd4\xa0\ -\xea\xec\xed=9\xd7\xe0\xa4I\x9b)\xc16\xe6\x8b\x86\ -\xb9\x99\x13\xec\x06\xbb2q\x0cllJM}\x82A\ -\x12\xa5\x84M\xe3\xee\x01\x98\x1cR\xbb*\xe7\x90,1\ -j#\x95Y\x90\x18\xa3\xe1\x1e%\xdb\x8ft\xc6\x97\x93\ -\x22\x88\x16T\x19\x92S*\xa0[T\x90\xc4\xe5\xd2\xc9\ -AU\xb3nc0R\x8f2p\x0b\x9e\x0c\xc4\x1f8\ -g\xd9\xa57\x18\xd3C\xd4\x8fR\x82\xd3}z\xdc\x0f\ -\xc9\xf9\xd5\xe5\xcd\xff\x1a\xcd\xdf\xad7\xbc\xa4E\x8c\xfd\ -P.\x89{\xee\x9a\xde\x94\xba\xda[suB~\xf5\ -2\xff\xb4\xfc\xc7?l\xca\xf4\xe2\x87\x92\xbbv\xf7{\ -6\x9fN\xfe\xd9\xbd\xbc\x9e\x5cw\x8b\xeba]\x1e\xbc\ -D\x0d\x9c\x04\x00\x9c\x97\x19E\xf4,\xdf\xcb\xe9\xe4\xf6\ -z\xbcN\xfc\x7f\x93\xcb\xeb\x96\xfa\xfe2O\xc0\xaf.\ -\xf3\xd7\xcbU\xf1\xf1h\xf6n4\x9d\x8e\xee\x9a\xd6\x0a\ -\xb5\xdeg\xcc\x9c\x01eA\xee{\xb86\xef\xcf`\xfd\ -\xf5d\x98,DB\xc7\x01YH\x1cMN^\x9f\x0c\ -\x11,\x88\x22\xd2\x8a\x8a\x99\x88\x16,!\xc42\xafu\ -t4\xcdD\x92 \x22J\x83\x98\x828\x99R\xad \ -\xc6\x90\x89\xce\x03\xb6LN)\xc5B\xb4\xc0\xcc\xae\x03\ -\xf1@D\x8a\x0b\x22\xa6\xe2a\xd5\x83YbH'?\ -\xaf\x93S1L\x02\xc6RoOF\x80\xe0\x16\xcd?\ -5\x96\x18\xb0\x109 9\x15\x22\xa5\xe0\xee1\xf5\xc4\ -R\xafr0\x11\x8a\xd2\x93K\xbd\x0a\xc1 r\x92\xf5\ -*\xa4t\xd7S\x92Uc\x0d\xb1\xef\xd8\xcf\xeb\xe4^\ -\x8c\xd7\x95\x8c\xc9Me%2\xe5\xc60\x80\x83\x99\xac\ -\x83\xa3)\x13\x1d\x00[ M\x83\x0b\x89\xe3\x0a\xf4<\ -\x12n\x99\x978\xd9rx8m\x1b\xc8_O\xd6\xc7\ -\xb7\xb8)G@.\xd5&\x08\xc9\x89\xc5Wd\xc9\xd5\ -r\xd0\x18%c\xc3\x12\x12Z\xae\xd4R0e4-\ -\xae\xd7\xdcH\xb4\xf6\x8aC\x12\xb2,\x95\x04I\xae\x9e\ -I\x14\x04\x05-\x0d\x14\x83\xc7\x18\x9d*\x91\xc5R\xe4\ -\x82\x0a$\x00\xaa`!Q@\xb1hk\xe4\xd7\x0b2\ -\x00\xd2\xaa\x8a\xaau)h\x02\xd7e[\x96\x0a\x91\x83\ -[bn\xbb\x85\x08\x01\x10\xc0\x17\x02x\x92L\x84\x18\ -T\x11u%j\xdc\x8a\xca\xaf\xf7\x96\xc47\x9f\x9c\x88\ -\xf5\xe4\xfe'\xff\xeb\xc9\xb8\x9b\xdf\xddt\xb3\xecof\ -\xe7\xf53[\xfc[[7/\xbd\xce\x7f\xaf\x00\x92R\ -@F\xb6#\xc7\x8f\xf8\xd5\xe3\x07\xa5\xc7\xc4\x8f\xc3\xb4\ -e\xbb\xcemzit.`\xb3\xb4n:\x95q\x17\ -\xd3u7M \x81@\xd4\xd6\xdd4!eN m\ -\xbd\x0b\x11d\xb2\xb1\xaf{\x22\x22\x0e.\xa9wY\x0b\ -\x9aB\x22k\xbctOn\xbctO^w\x9cD\x18\ -\xa2\xb0\xd9\xba\x8b%\xb4\x10!\xff\xb5^\x1a\x85\x02j\ -rk\xbd4\xb2\x07D\xbf\xe7\xe8\x99\x83klBB\ -Ol\xbctOn\xbct%+F^s\xd2\xb5-\ -N\xc2\xd8D0\xd1 @\xca-\x8c\xa8\x1cH\x8dh\ -\xddI\xa3q\xe0d\x18{'\xdd\x8cc\xe3\xa5{z\ -\xe3\xa6\xd1<\xc4d\x90\xd6\xdd4\x1a\x05\xc8\x09\xeb\xdd\ -4\xaa\x054w\xbc\xe7\x0f\x95BT\x8bi\xcdO\xa3\ -\xa4\x90(9\xae\xf9\xe9J,\xa5\xac\xf5\xd3\x04)\x88\ -\x11`\xeb\xa7+\x19\xd9t\xddO\x13X@\x10Yw\ -\xd3\x04\x14(1\xf8\xbdn%\x0bLl\xd4\xb8\xe9\x84\ -\x01l- \xc5\x16\x95\xdeM\x1f2C\x8b\xdf\xde\x0c\ -\xed\x89\x03\x88\xc5 \x82\x84\xba-\x80\x807\x01Dc\ -\x00F3\xf9\xf2\x00\x82\x01\x81\xc1]\x06\xcbPQ\x97\ -}\xaal\x9c\xb4\x5c\xbak\x8cd\x83\xa2\x08\xccJq\ -\xa0\x14Hb\x02\xfd\xf2\x00B\x0cO\xb1\x00i\x0b\xaf\ -\xaf\xd6\x87\xc5\xb3*\x11\xb6\xb7\xa6\x14\x02\x03\x81\xb6?\ -\x83(\x070E^\xeb\x226\xbc\x07J\xbfy/\xc9\ -\x1f$\xbd\xaa\xf1\xa3\xa5?\xa8\x93\x15#o\xa0\x19*\ -4\xa0 H\xf0\x08\xc2\x0d(\x99+$\x16\x12\xdc)\ -/\xc7\x87\xddO\x8av\x94\xd1&\x0c\x10\x91\xa4\x11(\ -b\x88\xa6j\xde\x88e\x99\xd59\x817b!J\xe8\ -y\xf7\xdet\x83@1\x12Q\x1a`H(\x90\x18_\ -\x1cAK\x1e6\xc9Rf=\x0aj\xac\x81\xc8]R\ -\x03\x9b\xa4L\x15\x07ka\x0b\x9e,\x12Z\x03[\xcf\ -\xfbP\xd8\x94\xa2f\xd8\x8e\x03\x17~U\xb8\x9c\x02P\ -ri=\x8aq`6Sj\xe0\x1a\x92\x86\x14UI\ -[\x97\xc2_,69\xea\xe1b?\xbd/\xf1\xa01\ -\xb2\xb7\xe0`\xca(p\x8c\xda\x80#\x10\xc4A\xb9\xd1\ -\xa5\xc2\x0bI\xcdd\x87\xf0\xaa\x0e_\xc5\x91\xeeW\xe5\ -A^\xe5v\xe9\x03p\xbb\ -\x8fcs#\x802\xe8\xe9\xc1\xdb;\xc0\xb1\x8b\xdd\xc6\ -~\x9d\x93\xb3\xfd\xad\xf9\xe9\xc1\xbb\x18\xa2\xdb\xb8\xe3\x8d\ -\xd6\x8e\xb4UI\x99u?J\xec\x87\xa3tQ?\x8f\ -AI`\x1bJ\x87\xb7\x06\xc7CI#\xeeEIc\ -<\x1c%\x83h\x00\x8f@I\xa3\x1c\x8e\xd2\x9b\x0b\xe9\ -\x00\x9e\x0e%\x8e\xfbQby\x94.\x8d\xc7\xe7\x8fA\ -\x89\xed1\xba4\xbex\xf3\x84\xba\xa4z\xc0\xe6\xc0t\ -q\x8e\x17\xf4\xc8\xcd\x81\xaa\xea\xfbQ\x8a\xe8[57\ -\xc8n\x9c\xc4\xad{#\xfbFe\xb7+=\xa7\x91\xf8\ -\x03\xa4\xc3\xad\xd2\x19\x1coT\x8c\xf7\xeb\xae=&Z\ -\x00*\xfbct\xd7\x1e\x11-\x10G\xe9\xe9\xfc`\xa4\ -h\xfbP\xca<\xe9p\x94\xc6\xd2\xe9c\xa2E$\xc6\ -\xdfZ\xb4\xa0\x94`\x1fJ\x99\x87\x1e\x81\x92\x95\xbf\xc3\ -Q\xa2\x94\xf88(\xe1\x0e\x94\x0e\xdf\x0b\xd2n\xd5\xc0\ -'\xda.%*$N{\xb6j\xe0\x96\xedR\xcf\xb6\ -/\x88\xf0x\xbbB\x87\xf7\x9f\xc0\x8d&\xcd\xa3\xb7G\ -\xdeZ\xf4\xac\xcf\xd5\x12\xe1\xf1\x80\xdb\xfa`\xedp\xdb\ -\x93\xb5\x11\xe8\xd9\x9e\xac}\xce\x07\xc5\x89\x8e\xb8\xad\xef\ -\xf8[R\x9fu\xdb\x01\xc9\xef\xcf\xd0?\xb1\xad\xdb\xf1\ -l}\xfb\x86\xb4\xcf\xecG\xa3(\x01\xcc\xb1\xd9\x8f\xf6\ -U\xb6\xd6=\xe3\x86\xa3H\x94\xbe\x83=\xbc\xc7\xf7\x81\ -\x91~7\xf4\xa7=\xf2!\xf2\xb7\x7f\xe4\xc3W>W\ -\x84\xa2~{\xe7\x8a<\xabw\x8b~ `\xfb\xb7=\ ->\xd5\xce\xcbg\xc5\x89\xe1y\xb7\x9d>\xeb\xa4\x8e\xe9\ -\x98J\x82\xd1\x83d=y\x9a\xfd\xa9\xcf\x1c\x16\x99\x8f\ -\x0d\x95\xb9q\x0e\x84\xb0@\x22\xb5\xb8\xb9\x05Vg\xf5\ -\x06\xb7\xcc\x1d\x15\xd9I\xb4U2\xd4`\x05\xba\x069\ -\x04\xad\xd0\xe9\xf3\x22\xa7_\x179L\xfa\x09\xba\x069\ -\x8d\x9f\xb8\xa5\xc1M\xa8j\x9czk\x9c\x1a\x88\x98\xc5\ -\x9es\x9a\x00\x88\xcf\x11\xf2\x1cB\x8c\xc8\xdc\x04\xbe$\ -\xc1@\x10\xb0\x89\x7f\x9b\xbc\x17\x0do\x13\x0c\x9f\x17J\ -\xfb\x1d\xcacA\x99~\x87\xf2HP\xd2\xf1WZ\xc9\ -\x01\x19R\x01\xd2+\x9a\x9c\xc0\xf0+\x81\xf9\x9cS\x19\ -`h\xc3\xa7\x06aC\xdf\x98u\x08\xd3\xbd\xc5\xb8s\ -H\xac\xf7g\x1df!2D\xf3\xdf\xfe\xc9\xde\x15\ -Zc\xb4\x85@\xb2f\xaa\x0d\xdc\xf7\xa9w-\xb5\x81\ -ze5\x9b\xc6R\xe9\xef\xbb\xf9h<\x9a\x8fz\xcb\ -YRH\x11\x97\xbd\x9f\x8e/^\xfe\xef\x9f\xfe\x5cR\ -5}~\xfe\xf2\xff\xe47&\xf5\xcf\xbb\x15\x86\xd1\x9b\ -\xc9mF\xab\x96Y\xf0\x8d\xcf_\x16\x8f1\x9a\xff\xe9\ -\xf2}\xb6\x87\xf2\xae\xac\x7f\xcd\xaf\xac\xca6\xbc\xcch\ -\x99\x0b \x99\xd0T;\xed>\xbd\x0bk\xdb\xeb\xc3\xf2\ -\xbf\xf7\x97\xa5\xd0\xd9\x7f\xcd\xf3\xe9!\x7f)\x8d\xac?\ -\xadx\xb6\xe8h&\xd4d/GN,\x05\xad\xa9\xb7\ -\xf7\xc60\xbf\xb7\xa9\xbbzu\xfa\xf3e~}\xd5\xd5\ -\xe4\x047\x95sr{\xf3~2\xee\x16&\xb4\xcc\x1f\ -/\xd2\x05\xba\xfe\xa8\xa1\x1d\x07\x0d\x95\xc3\x85\xfaO\xa3\ -\x05\xa5(\xb1{!\xf6G\x0cY\x0c\x84n2@\x0a\ -\xa8\xc2\xac\xf9\x04\x18\x09\x22\x98x\xa0\x18\x90\xcd\xbc\x1c\ -J\xc3A\xd4\xb5\x90\x5c\x22\x08\x9d\xe4l\x0a\x9e\x1cX\ -\xcbu\xe5\x19\xa8\xd4-_\xc2+~\x0d\xc8\x9a\xd8\xfb\ -J5\xb8\x10ai\xc8\xea\xc3z\x94[G\x09N\x12\ -1\xd7b\x1e\x80S\xb4L\xf4\xc0\x22v\xe2\x5c\xaf\x9d\ -\x9c\xe9S\x22\xe6\xc2\xf9\xc23k\xf9pN\x22j\x9f\ -\xce\xd5\x94\x12\xd1\x09\x96\x5c*~\x82\x04\xe5\xd22\xa7\ -\x06\x10\x04\xc1B\x1bx\xae\xa0^p)\xd9s1\x86\ -\xc4\x9ed\xad\xb2\x98BJ)2\xd7\x06\x19jGV\ -\xdf(!\xd5O\xae\xc6\xb9\x82\x0a\xee\x1aOL\xebI\ -+>H\x81Y\x0d\xc4\xb7A_O\xb9\xd9~\xc6\xcd\ -\xc6Q\xec\x9b\xa7\x10\xd6\xe4\xf4\xf6\xaa{\xd9\xfd\xd2e\ -\xa5\x18\x7f\xfe\xd4\x1b\x0fJ\x18\xcd\xd3\x13\x9ey\xd3\x1e\ -ns\x1c\x05\x8e\x12\xe9w\x05\xfe\x86\x14x\xa9\x02\x10\ -HX\x1d\x95\x7f\xf8\xec\xb9M\x12\xf9\x1b\xd7\xe9\xfdv\ -\x1b\x01\xbfDF\xbc\xf9x_\xba7\xb7\xf3\xf9\x86p\ -U\x9e\x05\xb5m)\xd3?u\xac\x7f\xeb\xc7\x8b{\x16\ -\xa5\xa1\xe8s\x00%)\xda,\x81\x89\x12R\xa1!\x99\ -yV\x06\x0e\xd1\x15R*4\x8e\x89\x5c\x8b6\x14M\ -(F!\xa1jE\x0aJ\xb2P+\x0d\xacn\xab\xd4\ -B\xbbb\x00Ge?q\x08\xa0\x08\x96+\x84\xe0\xac\ -\xaam7~\xdd\xf4\x04d\xd4\x0e\xc1\x06\x87`j%\ -+F\xaa\x82HE\xdd_\xd7dDT/f\x82\xc2\ -\xa2x\x82\xb5O\xae\x99E\x03\x92l\x10\xaa\xa1\xa6\xe0\ -qA\x82\x14\xad!V\x13\x5c$S\xf4\xd6\x04\x89@\ -\xb51AY\x94*\x06\xd8tp\x97-)2E\xa2\ -\x1f>\xabfB\xf8\x0d\x98\xd2.\xbdd^[0\xcf\ -?\xf3\x83\xd8\xb0|!y\x19\x98\x17\x0f4\xc8\xcd\xd7\ -L|CH\xfd\x1e\xfb\xbe\x81\xd8\xd7{ f;=\ -`\xb6\xb3\xdb\x9d\xc5t\x7f\xf8\xa9u\xd4\xb8\xc5Q\xc3\ -\xa6\xa3\xd6\xb4r\xd4x\xcfQC\xe3\xa8a\xe1\xa8a\ -\xcdQ\x9bn8\xea\xa6\x1b;\x9c\x96\x01#&\xb7\xcf\ -{\xad\x98\xe2o88n:!\x08\x1eQ\x19i\xe9\ -\x8c\xd0*z\xd5%\x993\xca\x8b\x9d\xc3\xbfk\xd87\ -[\x1bR\x09\x15\x84\xd1\xeb\xcbg\xa0(\xab$k.\ -\x1b\x16\xd4\x18\x10\x1dd\x80\xe2\x81]%\xbe\xd81<\ -\x9c@\xa3\xca\x8e\xa0\x12\xe1;\x08*\xdf\xfe\x94\xa0\x9d\ -\xe4\x90\xf7\xc4\xad/\x80N\xf0Y\x86\xbb\x05C\xaf\x82\ -o\x1f\x1a7\x01\xf1\x90CV\x9fd\xcc\xf7\xce\x0f \ -\xb8Br\x95\xb5\xb7\x81q2\xd3\x17\xed\x0d\xb2\xc8F\ -\x07\x9d\xe7\xdc.Q7\xdc\xbeq!7z\xe6\x18D\ -\x80y@\x18X9q\x1d[\x0b\x02\xc26\xb0\x18\x1c\ -4I\xaaz\xa2\x011i\xa6J`T\x01\xac\xb4\xc8\ -(\x03\xd3\xe0\xe4\x18\xad\x16\xe6z\x86\xb2y\xf5\xc9\x8a\ -\xa5\xca\xe2\xad)\x050W-\x95\xd5i6\x05U\xf0\ -\x9c\xf0\x9apAO5\xd5\xb3\xfe\x5c\xd3)\x05\xa2\x84\ -F9M\xc4\x1bi\xa1`\x06\x1e\x17\xfc\x1bi\x8a!\ -Q\xa2H\xb9\xe9\x9a\x86`\xd1\xa3\x94\xd688\x1b\xc6\ -\x12nH\x98\x15\xef\xdd\xf7\ -\xcb\x17\xfdz\xbd\xde}[u\xbf\x17\xd3VbV(\ -l\x09E\xafmUHC7\xaa+x\x0f\x14\x93z\ -M\x10T\xf6\xe5\x22?-\x0b\xd7\x94-\x18\xd7k\xd2\ -EM\x0d\xf0\xf5\x1d\x16\xa5\x08\xc7\x90\x8c0\xa5O\xf5\ -1\xf7ID\xddH\xb7\xec}r\xdb\xea\x06X\xf7\xaf\ -\xa0\xfb\x9b(_n1\xf8\xbdZ\x8c\x07P \xb3:\ -z\xbd\xe3\x93\xa8\x5c\x07\xd8\xc0\xc0\xa8\xd0R23\xaf\ -j'\x86QW4+ZT\xfd\xdf\x1a\x89\x177\xc2\ -\x0a1-\x89\xbd+\xec\x89\xb5Bfd+4s\x82\ -j\x991&\xe7\xb8\xd1\xbf\xaa\x5c\x0b\xf5\xa9z\xc3\xb5\ -\x1a/6\x19\xb1\xd6\x9c,$fK\xf8T\ -\xa9\xd6\x83\x8f,\xa4oBP\xc1f\xab\xd1\x08\xef7\ -\x9aj5\xb3\x95\xc9\xd4i\xcf\x8f\xf9\x91\x8a\xfc\xf5\xff\ -\x01_\x1a\x15U\xdc\x8d\x00\x00\ \x00\x00\x05:\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -3843,78 +1523,6 @@ gSuV\x00\x8d\x8d\x8dn\x8b\xc5\x82\x81\x81\x81H\ mm\xad377WV\xd3\xdd\x00\xf8\x7fFL\xc2\ A\x99n\xd7\xdfC9V\x18\x85p\xc8\x04\x00\x00\x00\ \x00IEND\xaeB`\x82\ -\x00\x00\x04_\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\xc8\ -PLTE\xe1\xe1\xe1\xda\xda\xda\xdc\xdc\xdc\xdb\xdb\xdb\ -\xf8\xf8\xf8\xff\xff\xff\xe7\xe8\xe8\xeb\xeb\xeb\xeb\xec\xec\xec\ -\xed\xed\xed\xee\xee\xee\xee\xee\xee\xef\xef\xef\xef\xef\xef\xf0\ -\xf0\xf0\xf0\xf0\xf1\xf1\xf1\xdd\xdd\xdd\xf4\xf4\xf4\xd7\xd7\xd7\ -\xd8\xd8\xd8\xd9\xd9\xd9\xdb\xda\xdb\xda\xdb\xdb\xcc\xcc\xcc\xe2\ -\xe2\xe2\xe8\xe8\xe8\xec\xec\xec\xf1\xf2\xf2\xd6\xd7\xd7\xea\xeb\ -\xeb\xe5\xe7\xe5\xd0\xdd\xcf\xe6\xe9\xe7\xed\xed\xed\xea\xea\xea\ -\xf3\xf3\xf3\xe9\xea\xea\xdd\xdd\xde\xde\xe6\xdf\x9e\xc5\x97\x82\ -\xb7{\xdf\xe8\xdf\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf5\xf5\xf5\xf5\ -\xf5\xf5\xf6\xf6\xed\xec\xed\xb8\xc9\xb7\x8c\xbc\x80\xa7\xd0\x87\ -\x8b\xbd}\xdd\xe7\xde\xf4\xf4\xf5\xf6\xf6\xf6\xf6\xf7\xf7\xf7\ -\xf7\xf7\xf5\xf5\xf6\xf0\xf2\xf1\xaf\xcd\xad~\xb4e\xa6\xd0\ -t\xb6\xd9\x81\x81\xb5r\xde\xe8\xdf\xf6\xf6\xf7\xf8\xf9\xf9\ -\xf9\xf9\xf9\xf1\xf1\xf2\xe5\xeb\xe6\x92\xbb\x85|\xb4I\x9d\ -\xcbR\xa3\xd0U\x9e\xcbSs\xa9\x5c\xde\xe7\xe1\xf8\xf8\ -\xf9\xfa\xfa\xfa\xfb\xfb\xfb\xf3\xf2\xf3\xdc\xe6\xdeu\xa7_\ -r\xad\x1a\x90\xc9\x19\x95\xcd\x1a\x8a\xc5\x13d\x9e;\xde\ -\xe7\xe2\xfc\xfc\xfc\xf2\xf2\xf2\xfa\xf9\xfa\xe1\xe9\xe3\x87\xad\ -uu\xae\x16\xa6\xdf\x0a\xa0\xd9\x0ag\x9d4\xde\xe6\xe2\ -\xfc\xfb\xfc\xfd\xfd\xfd\xde\xde\xde\xf9\xfa\xfa\xfd\xfc\xfd\xeb\ -\xed\xed\x92\xab\x8fu\xa6.\x95\xcc\x08i\x9b0\xdd\xe5\ -\xe2\xfe\xfe\xfe\xf2\xf1\xf2\xe1\xe0\xe2\xc5\xd2\xc9j\x92K\ -N|1\xdd\xe5\xe1\xe2\xe7\xe4\xa8\xbb\xac\xef\xf2\xf0\xde\ -\xdf\xdf\xf9\xf9\xf8\xf0\xf1\xf1\xdc\xdd\xdd\xdf\xdf\xdf\xd0\xd0\ -\xd1\xd2\xd2\xd2\xd3\xd5\xd6\xe1\xe2\xe3\xd4\xd4\xd5\xc5\xc5\xc6\ -\xcc\xcd\xcd\xc8\xc9\xc9\xe0\xe1\xe1\xbc\xbc\xbd\xe3\xe4\xe3\xd9\ -\xda\xd9\xdf\xe0\xdf\xe5\xe5\xe5\xe6\xe6\xe6\xbc\xbd\xbd\xd5\xd5\ -\xd4\xd1\xd3\xd4\xc5\xc7\xc7\xe5\xe5\xe4\xd3\xd3\xd3\xe0\xe0\xe0\ -\xdf\xe0\xe0\xd9\xda\xdb\xca\xce\xd0\xe2\xe3\xe4&\x9b[?\ -\x00\x00\x00\x01bKGD\x05\xf8o\xe9\xc7\x00\x00\x00\ -\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0k\ -B\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x14\ -2\x1f\xc9N\xa7\x00\x00\x01[IDAT\x18\xd3M\ -\xd0US\xc3@\x18\x85\xe1\xcd6\xa1JH\x03\x05J\ -\xd1\x12\x8a\x14\x82S\x82KpIq\x08\x0e](\x1a\ -\xdc\x83\xbb;\x7f\x97\xdd\x0c\x17\xbc\x97\xcf\xec|;s\ -\x00\xa0\xe0\xbfL4\x83\x03T\x84\xd9b\xb5\xd9\x1d\x91\ -,\x1b\x15\xc59y\x03\xa1\xd9\xc6Z\xa2c\x5c\xb1\x14\ -\x15\x17g\x8a\xa7\xdcn\x90\x00\xa0\x87M\x8cHJN\ -IM\xb3x\xac^\x8f\xd7nO\xa7\x00\xb4qB\x86\ -/3+;\xc7\xef\xcf\xe5\xf3D1\x9f\x83\x00:\x04\ -\xb1\xa0\xb0\xa8\xb8\xa4\xb4\x8c\x17\x03\xe5\x92$\x09\x18\xd9\ -\x8a\xca\xaa\xea\x9a\xda\xba\xfa\x86\x80D7\xca\xb2\xcc;\ -\x01ljnimk\xef\xe8\xec\xea\xa6e\x05\x17\x14\ -1\xf6\xf4\xf6\xf5\x0f\x0c\x0e\x0d\x8f\x10\xc0\xa9\x01\x8c\xa3\ -c\xe3\x13\x93S\xd33\xb3!\x0c8$\xcd\x01(\xcc\ -\x87\x17\x16\x97\x96WV\xc3D\x10\xd2h\x8c\xbc\xa2\xae\ -\xadolnm#L\x9a\xa612\xc1\xa0\xca\x81\xd0\ -\xce\xee\x1e!\x06k\xe3\xbe\x81\xa3@\x09i*y\x86\ -\x0ex\xe1\xf0\x08@\x91 \xf9\x03c\x90\xd3\x8fON\ -\xcf\x08\x22\x03\xb1!\xee\xfc\xe2\xf2\xca\xa7bT\x91\xf0\ -\x87\xf2\xf5\xcd\xed\xdd=b\x80\x13<<\x9a\x5c\xaa\x81\ -\xe2\xc9\xd33\xd0\xf0\x9e\x8a$\xd1\x1e\x9d\x5cd\x18\xfe\ -\xe5\xf5M##\x93\xa5\xc5w]\xd7?>>\xbf\xbe\ -\x7f\x881\xbf\xe0\xd3^\x99\xad\x16c\x0b\x00\x00\x00%\ -tEXtdate:create\x00\ -2023-03-08T17:27\ -:26+00:00\x9e\x98\xa5\x85\x00\x00\x00\ -%tEXtdate:modify\ -\x002023-03-08T17:2\ -7:26+00:00\xef\xc5\x1d9\x00\x00\ -\x00(tEXtdate:times\ -tamp\x002023-03-09T\ -06:20:50+00:00\xdd\xe8\ -\xb2\xde\x00\x00\x00\x00IEND\xaeB`\x82\ \x00\x00\x08\xab\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -4056,256 +1664,6 @@ e:timestamp\x002023\ tSoftware\x00Adobe \ ImageReadyq\xc9e<\x00\x00\ \x00\x00IEND\xaeB`\x82\ -\x00\x00\x07S\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x06\x00\x00\x00\x8c\xfe\xb8m\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x06\ -bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\ -\x00\x09pHYs\x00\x00\x00`\x00\x00\x00`\x00\xf0\ -kB\xcf\x00\x00\x00\x07tIME\x07\xe7\x03\x09\x06\ -\x12)\xc3\xf6 \xcd\x00\x00\x06\x1eIDATX\xc3\ -\xed\x98\x7flUg\x19\xc7?\xcf\xf3\x9eso\xdb{\ -KK\x91\x14j\x99\xb0\x91at\x85L\xcc\xdc\x92\xa9\ -Yf\xf6\x07\x84\xb9\xbf\x88#F\xe3\x8cN\x83\xc6D\ -\x13g\x8cQ4j\xfckJ\xc4-Y\xd0?dK\ -\x8c\x083\xcb\xe6\x88\x8e\xe8\x00\x8dc6\x94Z\x0b\x12\ -f\xa1\xb2\x8c\x1f\xa3\xf4\xd7\xed=\xe7=\xe7\xbc\xaf\x7f\ -\x9c\xd3R\x12\xe7nK)\xfb\xc3\xe7\xe6\xc9\xbd77\ -\xe7\xbd\x9f|\x9f\x1f\xef\xf3\xbe\xe2\xbd\xf7\xbc\x83Mo\ -6\xc0;\x0a\xd0y\xcf\x5c\xe3\x15,\x06X\xe6\xfcnJeC-\x8a\x11\x04\x11\x01\ -\x91\x19\xb8i\x9c$\xc9\xf0\xce5\xca7?\xc0\xcbc\ -\x11?\x7f~\x90\x83\xbdg\xe9\xb9m\x09k\xba[q\ ->#\x8a\x1c\xa2\x8a\x88\x5c\x85\x9c-\x1f\x90\xa6Y\xa1\ -\xe0\x0d\x02\xb3\xf5\x01~\xf2\xd8\xa7h\xaf_\xa0\ -\xaf\xef0c~\x8a\xd4y\xacM\x88cK\x14O+\ -\x18\x13'\x09\x19\x8d\x8f[\x0bv\xf5\xb1q\xfd\xed<\ -\xf1\xbd\xedlY\xdfM\xff\x91\x03\xbcv\xf9\x0c\x91:\ -\xe2\xd8\x12GW\x15\x8c\xec4\xe0\x22)8\xdb\x96-\ -]\xc27\xbf\xbc\x8d\xef\x7f\xfe!j'^\xe5\xd5\xfe\ -?sE-q\x9a\xe6pqL\x94$d\x8b\x95\x83\ -\xffuAU6}\xec\x1ev\xff\xf0+\xdcY\xcdx\ -\xe5\x8f\xbfe\xb8~\x89HL\x9f_\x0c\ -\xc3\xb2\xa6\xef\x1f\xed\x0e\x00\x18U~\x15Z@\xa3?\ -\x8f\x22K\x03\xe7l\x909\x00\x00\x00%tEXt\ -date:create\x002023\ --03-08T17:27:26+\ -00:00\x9e\x98\xa5\x85\x00\x00\x00%tEX\ -tdate:modify\x00202\ -3-03-08T17:27:26\ -+00:00\xef\xc5\x1d9\x00\x00\x00(tE\ -Xtdate:timestamp\ -\x002023-03-09T06:2\ -0:50+00:00\xdd\xe8\xb2\xde\x00\x00\ -\x00\x00IEND\xaeB`\x82\ -\x00\x00\x03\xa3\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00(\x00\x00\x00(\x08\x04\x00\x00\x00&\xf7p\xe6\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\ -bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\ -Ys\x00\x01bT\x00\x01bT\x01\x80\xc1\xe1l\x00\ -\x00\x00\x07tIME\x07\xe7\x03\x09\x06\x12)\xc3\xf6\ - \xcd\x00\x00\x02rIDATH\xc7\xad\x97\xbfo\ -\x131\x14\xc7?w\xbd\xa0\xdc\x80rBJ:4]\ -*\x90\xf8\xf1\x07Db\x00\x81`\xc8\x8e\x98\xb21e\ -a({\xd35K\xc4\x10\xa9\x13[\x84\x00\xc1L\x85\ -X\xca@\xf7\xa0\xa2J\xa4(R\xc4\x90FB\xad\xa0\ -\xbd4Ic\x86\xbb\xfc\xb8\x8b\xed\xb8\x0d_/\x96\xfd\ -\xfc\xbd\xaf\x9f\xfd\xde\xf3\x81\x1c.E\xea\xf4\x10\x92\xd6\ -\xa3N\x11\x97\x0b I\x19_J6j>e\x92\xb2\ -\xa5\x96\x94\xb0\xc0+\xae\xf0\x93\x8ftb6\x02H\x93\ -g\x8d\x1e\xcf\xa8\x99\xe9s\xd9Fp@Ni\x91\xe3\ -\x00\xc1\xb6\xe9\xb6Wh \xa8jm\xaa\x08\x1a\xac\xcc\ -N\xd8\x12c\x87\x04\xd0\xd1\x12v\x80\x04\x8e\x19a\xe0\ -\xa9\xf9\x90Z\xd9Js\x0b\x1d\x94\xb32\xc2\xa1\x81J\ -1e\x19\xf3W\x00\x97ka\x7f\xc8*\x0e\xe0\x91e\ -IAw\x8e\x078\xac\x8e%\x0d\xf8\x8d?\x92\x9e\xe4\ -\x09\x05\xae\x93\x08\xbf\xeb\xb0\x8c\xc31G\x1a\x85\x1e)\ -\x06\xb4\x19\x84\x0e\xe8\xd3\xa0\xc6{\xba\xe0R\xe6L\x1b\ -\x15\xa6\xed\x8c2\xaeE\x91\x0aIiT\x98b\x12=\ -]\xd6\xa1>'*L\x11DO\x1dzs\xa3\xc2\x14\ -U\x04=\xdb *L\xd1\x01\x12\xf6\xc241\x04\x84\ -\x979\x8aYX\x13\xc2\xff\x88x\xbe\xb0\xc8\xc83\xb1\ -\x02]\x0e\xa3A\x1a'|L\x85\xab\x86\xd9\x06,\xfe\ -\xb0\xce'\x1d\xe1M\xee\x5cp\x8f\xb7\xf4\x84o\xb1\xb8\ -A\x81\x14_\xd8Qzx\xc8}\xeeqL\x8d\x1f\xbc\ -\x89O\x0a\x04\xa5\xc8H\x96&\x82\x0d\xad\xae\x0d\x04M\ -\xb2\x91\xb1\x12\x02!\xd3\xb0\x14\xfaG\xef\xbd\x89e\x04\ -6} \xad\x5c\xa2'\x8c\x22\x0d\xf4m\xf6\x81|$\ -9\x9cc\x8ai\xcb\x1cy`\xdfa\x8b\x0ak\xbc\x1e\ -\xa7/\x81\x877\xd6\xf1\x88\xbb\xb1B\xff\x95\xcf\xe1\xb5\ -\xf2x\xc1\x11V$}m\xa9\x13\xec&\x90aof\ -|\x8fe`S\x95`\x1d|J|\x93\x94\x00\x80\x13\ -vIE\xae\xb9\xc5.\x7f\xc3\xbe\xb4\x048@\x97\x1a\ -\x1f\x22E\xea]\xf8&8\xe19\x99\xc8m\x1cr\xc8\ -i\xd8o\xf3\x94V\xbcH\x8d.\xb6\xcf\xaf\xa9e\x83\ -q\xef\x94\xa6\xf2H\x06\xb4h\xc5\x07%\x8f\x89)E\ -\xfaC\x91\xe6*\x19\xe1h)\xa4y\xc9\xed\xd8\xf8w\ -\x1e\xd2V\xa7\x0f9\xe1H\x93\xfeP,S\xc2\xc18\ -z\xd4\x87\x92\x06\xfaS\xbe\xd6b\xa1\x07\xa7L\xa1O\ -\x8d\x07\x91\xe8\x89{6x\x12\xd7\x82\x8bb\x82\x05\x1e\ -\xed\xeam_\xf2\xb7\xe2\x1f\xabI7!\x97\xa94\x84\ -\x00\x00\x00%tEXtdate:cre\ -ate\x002023-03-08T1\ -7:21:49+00:00\xa3\xa1\xac\ -\xac\x00\x00\x00%tEXtdate:mo\ -dify\x002023-03-08T\ -17:21:49+00:00\xd2\xfc\ -\x14\x10\x00\x00\x00(tEXtdate:t\ -imestamp\x002023-03\ --09T06:18:41+00:\ -00=\x00\x9f\x01\x00\x00\x00\x00IEND\xaeB\ -`\x82\ \x00\x00\x06m\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -4496,364 +1854,6 @@ l\xb5\x08\x8c\xf9\x94\xf1\xe0\xf03K\x9a|h\x13Z\ \x918\x099\xf9\xa9\x96!\xfa\x5c\x1a\x0d\xcf\xb3\xff\xff\ 7\xfcO\x13\xf8\x1d\xe7\x87\x19\xb9D\xc3\x01\xcf\x00\x00\ \x00\x00IEND\xaeB`\x82\ -\x00\x00\x120\ -\x1f\ -\x8b\x08\x00\x00\x00\x00\x00\x00\x00\xed\x5cms\x1b7\x92\ -\xfe\xbc\xfe\x15<\xe6\xcb\xaa\x96\x04\xd1h\xbc*\xb6\xb7\ -l\xc9N\xa5*\xb9M\x9d\x93\xdd\xfb\xe6\xa2\xc8\x91\xcc\ -\x0dE\xaaH\xca\x92\xfc\xeb\xb7\x1b\xa3\x11\xe6\x85 %\ -\x99\xde\xad\xca\x1d\xc7\x96\x80\x06\xd0@?\xddh\x00\xc3\ -\x86^\xfe\xf5\xf6r\xde\xfb\x5c\xac\xd6\xb3\xe5\xe2U\x1f\ -\x84\xec\xf7\x8a\xc5d9\x9d-.^\xf5\x7f\xfb\xf5\xfd\ -\xd0\xf7{\xeb\xcdx1\x1d\xcf\x97\x8b\xe2U\x7f\xb1\xec\ -\xff\xf5\xf5\x8b\x97\xff5\x1c\xf6~(\x16\xc5j\xbcY\ -\xae\x8e{o\xa6\xcb\xb3\xa2\xf7\xe3|~\xbd\xdeDR\ -\x0f\x94\x90B\x0ez\x1f\xfe\xfeC\xef\xdd\xed\xd5r\xb5\ -\xe9\xfd2\xbf\xbe\x18\xfe\xb8\xe8\x89H\xfc{\xd9\xe7q\ -\xcf\x0a){o\xafg\xf3i\xcf\x80\xd6\xfe\xa8\xd7\x1b\ -\x0e\xa9\x8b\xf5\xe7\x8b\x17\xbd^\x8f\xc6\xb7X\x1f/\xd6\ -\xaf\xfa\x9f6\x9b\xab\xe3\xd1h\xb1\x16c\xeeNL\x96\ -\x97\xa3\x0f\xe3\xcf\xc5\xfb\xe5\xea\x1f\xc5\xd9\x08\x84\x1c\xf5\ -S\x8b\xe9\xe4\xa1\xc5\xd5\xf5j.\x96\xab\x8b\xd1t2\ -*\xe6\xc5e\xb1\xd8\xac\xa9:\xd4\xabOR\xf5\xc9\xaa\ -\x18of\x9f\x0b\xe2\x7f\xb9\x5c\xac\xb9%u\xfa]\xad\ -\xf2jz\xfeP\xfb\xe6\xe6F\xdc`\xac\x04!\x84\x91\ -T#\xa5\x86Tc\xb8\xbe[l\xc6\xb7\xc3fS\x92\ -j[S%\xa5\x1cQY\xaa\xf9\xb8Z\xc7\xb7\xf3\xd9\ -\xe2\xf7\xec`bi\xbdwR\xec\x15\xfd\x7fhP\x11\ -\xc4zy\xbd\x9a\x14\xe7\xd4\xb2\x10\x8bb3:\xfd\xf5\ -\xf4\xa1p(\xc5t3\xad\xb1!\xa6\xeb\xc9\xf8\xaah\ -\xf4[\x11K\xbc\xc6\x97\xc5\xfaj<)\xd6\xa3\x8a\xce\ -\xed\xeb\x96\x06\x910\x9b\xbe\xea\xff4\xbe+V\x1f\xcb\ -\xfc\xcdl\xba\xf9D\xc5\xca\xc7\xec\xa7bv\xf1i\x93\ -\xf2\x9fg\xc5\xcd\xdb\xe5\xed\xab\xbe\xec\xc9\x1e\x11{U\ -\xc1\x928\x9f\xcf\x977\xaf\xfa\x9fg\xeb\xd9\xd9\xbc\xec\ -\xaeX\x8c)9<\x1bO~\xbfX-\xaf\x17\xd4\xd9\ -\xa2\xb8\xe9\xb5\x1a\xb3T\xc7\ -\x9b\x174\xd6=}\x8f\x1e\x04!,\x95\xb8Z\x5cl\ -ev;\xbd\x22;SJ\x98\xad\xc5w\xa9\xf8\xf5\xcb\ -iq\xbe\xae\xac\x81\xd3 \x99J\xc6[\x8cW?\xac\ -\xc6\xd3\x19\xf5\xc6\xe5e\x8d&\x1d\xd19\xaa\xbc\xa6\x11\ -\xc6*\x8c\xe5\xe6nNB0i8Y\xce\xc9C}\ -\x87\x86\x9f\xef#iI\xaa\x9em\xee\x8e\xe1\xfb~\xd5\ -by~\xbe.\xc8\xce$S\xaa\x8e\x22\x03b\x1f\xfa\ -\xbd\xd1\xde\x0ed\xfc\xec\xed\x00\xb6t\xe0!v0j\ -\xca\xb5]\xfe;\xf5\xaa\x1f\xd8eJu\xcf\xe9\x96(\ -\xde\xd7)w@\xfdH]\x95\x03\xb7\xb8\xcf\x5c\xdc3\ -\xfbm1\xdb\x90\x8b\xb9&-\x7f`\xc3\xff\xdb\x82\x14\ -\xde\xcfC\x0c\xd8\x7f\xfd\xe2O%\x06\x8d\xb1\x03iw\ -; \xde\xfb7>\x96&tI\xce\x0c\x1b\x97es\ -\xe2\xdf\xf9\xd0d#\xa4Q\xc6hc\xf3\xfcB\x8e\xdf\ -\x9b\xb7oN\xdet\xf8Y\x87Y^\x0ar\xbcN\xcc\ -\x89=\xd1-^\x80\xda\xe5ya\x8e\xd7\xe9[z\xde\ -\xb4x)\x00\x93\xe7\x95\x85\xfe\xdd[~Z\xbcP\x82\ -\xca\xf3\xca\xe2\xff\xde\xd1c[\xbc4\xa8\x1d\xbcB\x96\ -\xd7)?-^\xc6\xba\x90\xe5\x85\x90\xe5\x15?\x0d^\ -\x10\xd9\xec\x9bG9#\xb7N\xcb\xfe#\xa6\xb9\x1e\xeb\ -is\x9a\xcb\xc7\xf8\x11b\xaf\x92\x1f\xe9\x16\xfa.\x0f\ -r\x8d;G\x82AO\xdb\x0e\xe7q\xae\x0a\xa6\x8f\x90\ -\x01\xb6\xc9\xa0\x1f\xeb\xaa\x92\xaf\xf9u5^\xacig\ -q\xf9\xaa\x7f9\xde\xacf\xb7\x7f\x96\x03\x18\x0ca \ -\x07C\x0c\x22\x04o\x06\xa0\xa5\x90R\x85\xa3~rs\ -C\xf4H\xa5\xce\xd4\x1c\xddP\xa10`A\x85\xaa\x22\ -\xa4\x8aPUd\x9a\xd7\xccPK\xfc:\xcf\xe7\x00\xfa\ -\xdbuFE\xb8C?7\x9ff\x9bb\xffzCp\ -f\xd9\x9b}\xec\xf7\xabp\xbb\xb6h\x9d\xdf\x14\xabX\ -9\xad\xcb\xc4w^L\xa8\xd1x~3\xbe[\xd7 \ -)\xab;v\xd2\xd4\xb6\xf8a|\xbd^\xcf\xc6\x8b\xb7\ -\xf3k\xe6\xb1\x9bK\x92`zZ|\x9e\x8d7q{\ -\xa2\x84\xf7\xd2\xf8\xe0\x1a&\xd6d\xed\x10B9\xfc\xb2\ -\xff\xd7/'\xf3\xd9\xd5/\xe3\xcd\xa7\xb2M\x95\xdb\xa7\ -\xd0\xaa\x9e\x01\x8b}b2[M\xe6E,\xae\xef\xba\ -Vw\xaf\xfah\xfb]\xfa\xedv\xfa\x84\xea\x07\xb5\x85\ -\xce\xf55\xb2U\x87\xd0\xd2\x1f\x039?\xa6\xc3\xc8\x9f\ -\xbfkj\xc4\x80uG\xdfsi\xb2\x95\x87\xb6\xab\xc6\ -\x00x<\xbc\xd8\x83\x94R?P\xb7\xf7\xda\x1a#\x8b\ -\xd8m\xca\x10\x95\x98\x18\xb0\xa6\x04\xbc\x82\xec\xb1\xaeS\ -\xab`3\xb3\x84\x8a\xb6x\xb6\xdd~m2>?\xcf\ -\xf9\xb5n\x07(e\xc6ue;P\xdav\xb6\x82\x8f\ -\xf4k\xfbm=\x9e\xb9\x8e?\xad\x0a:#~\xb7\x05\ -\xa7<\x88N=\xc1W\xb1\x93C\xe5\x05(\xad}}\ -\xe3\xa7\xa4\xf0\x00\xc6\xd7\xbc&\xa2\x15\xc1K\xe7j\xde\ -\xd5;\xe1L\xd0\x06I\xf0o\xeb\xbf\xa1\xee\xbfAQ\ -\xbd\x7f\x97\xf7\xd6\xce\xc9\x9c]:\xa7v\x19\xc9y\xfc\ -<\xdf\x7fs\x07z\xb7\x15\x9e\x9bb\xfa\x5c\x0f\xce\x89\ -\xf1\xfc`v\xc98%\x10\x9b\xcc}P\xf2\xb1Z\xe8\ -\xba\xa2\xae#:\x8f\x15\xacP:X[\xd1\xd85\xa1\ -\xf0DA6\xd2\xe4\xf7\xf6\x99\x22\x08\x1d\x14\xa2\x0c\x03\ -)\x94r\xca\xbb0\x18J\x81\xc4\xca(;P\xc2\x18\ -\x8f\xde#\xd9+\xa20\x80H\xa6\xab\x140\xdd\xa8\xa3\ -?\xb0\xf1\x9b`|\xc6\xf8M\xb0{\x5c\xb0\xd6~\xf2\ -|\xe3\xe7\x0e\xd4\xee\x0elq\xb6\x7fv\xc1\xb7u\xca\ -\x09\xa7,\x88>\xc0\xe3\x8f\xce\xcf\xb7\x9a\xa4z+@\ -I4\xbai# d\x08\xaaaK\xc6\x0a4\xce(\ -h\x18\x9dC^\x10\xd8\xa7?s\x97\xa7UP\x87\xdb\ -\xe5\x81\x95Z+\xcc\xef\xf2\xb8?]\xdf\xe5\xe5\xdfz\ -\xc8\x9a\xf4\xaa\xbe\xe2\x19'L\xc2\x90\xca\xbej\xda\x80\ -\xb7\xd6\xf3\xcb\x8e?u\xcf\xa3T\xc6\x8e2\xbbbL\ -&[l\xba5a\xf2\x9ci\xc6\xfa\xebD\xf0\x1f\xb3\ -\xbbg\xde\x93\x1ef\x03!\x82\xf3N\xa1\x1eH~D\ -\xf0@\x8f\x1a\x80@\x0bNjC)\xb0H\x8f>\xaa\ -m\xc5A\xaa\xda&\x1c@\x09\x94\xba\xb6\xc5\x06\x0cq\ -+\x1c\x0e\x08\x87\xdb\x09\x87\xf9:86\x9c\x9c\x8f7\ -\xc5\x9f\x95\xd0\xc5\xd0\x0e\xe4s\xc4=\x88\xb4[6\x0e\ -Y\xc1Qa8\x88\x1d@\xd4?\x08\x15\xc0\x82\xd4\x83\ -!\xfd\x03\x12\xca\x05\xf4u$P\x0a\x09\x12\xb0~\x06\ -C-\x9c\x91\xa0+\xda\xf9\x96z\xe7\xddz\x0c\x9c\x92\ -\xc2yP\xe6\xf9;KT\x1a\x0e\xb5\xb3\x04!1h\ -\x03:\xbf\xb3\xe4\xfe0\xb3\xb3<\x88\xbe\xd1\x01\xe4=\ -\x97\x06u\xa0s\x03\x01\x8f\xde\x83\xf5\x83aJ\xd29\ -\xc2\x08\xa3\x945\xc6\x0d\xac\x13\xca\x18\x1bZG\x89x\ -\xbch8\xf7! \x88\x80\x00\xaa~\x92\xd0F\xc8`\ -\x8d\xd7\xc9;\xa6\x9a\x87\xf1\xf4\xddoZ\xb2\xc8\xa1\x94\ -X\x17C\x0b\x08\xfc\xf1u9\x94\xaf\x09\x00\xca\x0bU\ -\x1f\xfb#O\x00\x87\x17LG\xb8\xf3\x82\x99\xff\xc8\xfb\ -\xbd\xe7\xcfX\xa9\xe5\xa1f\xac\x14A\x928\xde\xc3\xae\ -)+\xb5\xaaO\xd9g{\x9atT&s\x90\xc28\ -\x0d\x01M\x84?\xc5\x98(\x01\xda\xa3wP\xa1\xd6\xa9\ -\x9b\x02PR\xe5\xc3\xb9\xb0\x00\x18\x9c7;]\x98\xc2\ -\x83\xe0\xe1\xcc\xe1\xce\xf4\xcaxi\xf5\xeea;w\x90\ -a\x87p\xc0\x05\xc3j\x87z\xa7\xf5\xa1\x84\xfa\xb0\x0f\ -\xbdA@\x04\xcc\xef\x0f\x10\xa0\xb6z\x07%$\x00\x1a\ -\xac\xaf\xdeF\x04\xef\x94L\xabw\xb7\xdey\xb3^Z\ -\xbd\x0f\xb3\xf7\x10F*\x04\xe3\x06RxT\xa8iM\ -b\x1a\x06\x0bj\xa0\x84\x03m\xc0\x87\x81\x13A\x05c\ -%R1z\xa1h8p\xf4\x15~\x08\x1d\x1e\xce\x10\ -x\xd2\xe9\xb0\xd3~\xd1\x99ok\x08\xce\xed0\x04\x8f\ -\x8d\x0d\xadL&Pn\xd6\xbc3\x95\xb6\x1b\x15\xce\xbb\ -\x15x\xc7\xab\x1e\xb9\xa5d\x95\x82s\xd2S\xda\x08i\ -x\xf3\x80G\x07XI\x0f\xbe\xb9B\x0c\xfa\xff7W\ -\xcfD\xce\xfd\x87\x91\xb3A\xa03\xe8\xa1\x09\x9d\x13\x1a\ -\x94\x0eu\xe8\x94\x15\xd2*\xef\xd3\xae\xa6Q3NP\ -\x0e\x16|\xfd\xf2\xe1\xdbw\x0e^\x9cr\x8ch#\xe2\ -\xf0f\xb6\x98.o\x86\xd5R\xce\xefp\xb7\x95W\xa1\ -\xa7R\xa9f\xf9\xd5\xf8\xa2X\x7f\x1aO9\xbatK\ -\xd1\xfdKY\xde\xe3\x94\x8c/\xaeg\xd3b\xb3\x9c\x17\ -\x04\x1b\xc7\x94\xc2C\xc1j6\xddF_\x9e\xfd\x93t\ -\xbc\xad\xe4l\xb9\x9a\x16\xab\x87\x1e\xa0A\x8e\xef<\xc9\ -\x00l\xfc\xc4\x02\x1eOE>\x8f\x9f\x87w\xa1\x1cT\ -\xda\x1c\xfc\x97\xe5\xf2\xb2#Q\xe9t\x9c\x90\xd2j\xdf\ -*\xa2\x118)\xac\x0aA\xda\xad\x10R[\xcc\x14E\ -\xe7dZ\x0c\xafW+\x8e#\x9ds\x00p3\x0ex\ -\xfdiy\xc3p\xf1\x81\xff\xbax@o\xcdH,.\ -\xa8\x1f}u\xdb\xa6\xde5\xa9\xc5\xe5\xd5}\x01\x09\x99\ -\x98\xb2r\xd6\x89m\x1aM,\x19\x9eQ\x84kYJ\ -KN*#~\xa5\x15n\xee8\xee\xf9\xf6\x8e)\xb5\ -y\xc6Y\xa5e\xe5\x10:\xe3L\xb4\xbb:\xad9\xc6\ -h\xd2]c\xa6\x98\xb6\xcbb3\x9e\x8e7\xe3J\x99\ -U\xbe\x0c\xcb\xe4\xc8\xee\xf3\x1bJ\x95\xc9\xf9\x8c\xe2\xae\ -9\x97\xb2\xdc\x8e-\xb4\x0cw\xa6D7\xba\xba\x1b|\ -\x1d[\xfexJ\x04\x04\xeb4*L\xaf\xd2S?\xf5\ -^?\xc4(\xf2\xb7\x1ci\xbd~V\x97gK\x9a\x06\ -\x97?\x15\xe7\x9b\xbf\xadf\x17\xb3E\xa9\x09\xea\xb5\xea\ -fy\xb5\x99]\xce\xbe\xc4\xd5\xfcC\xb1\xd9\x10rI\ -\xd4\xcd\x98\xc2\xd77\x15\x99\xf95),K9\x0c\x8e\ -t~O\x0emLC\xf9\xe5\xbf\x7fP\xba\xcc0\x9a\ -\xf7\xbcj\xd4\xc8\x88f&i\x8a\xccurW3\x9e\ -r\x83P\xf0\x16b<_\x974j\xb1)N\xcaI\ -X\x0fS\x5c,\x7fN%\xa9>\x19 \xb1\x98\x8f'\ -\x89K\x1c\x05\xa3\xdc\x1c\x07\x91\x99\x98\x04}\x8e\xfc`\ -\x83uh\x94\x81?\x22\x10L\xceZ\x09\x95U3\xe5\ -%]\xd08\xfe\x9f\xd3\xf7\x14\x0f69\xfe\xc7r\xf5\ -{9\x1d\x99:>[^\x13\x18\x1c\x95>9>/\ -\xfb\x9b]\x92_\xe5\x9b\x17\x7f\xa1\xab\x02/G\xa9\x80\ -\xeb\xb0G\xe0\xe6\x15\x83UQ^\xa6\xd8v\xff\x84\xfe\ -]\xce\xb8\xc1\xe8\xc3\x86\xc2\xbd~d\xbe\xe5\xb4\xbf\x1f\ -\x07\xa5\x1e\x866\xaaf\xf9\xeb\x17/\xba[\xd0\xe8\x07\ -\xd2\x0b\xd7\xfa\x0b\xd0\xce\xeb\xcf\xd6\xcb\xcf\x17{\xd6\x7f\ -\x06\xfe\x09\xdf\xddIi\xd0\x9d6\xbft\xab\x87f\xb7\ -b\xad\xc1@\x8e\x13x\x8b>49\xf9<'\x12&\ -\xc7\x89\x8aN\xde\xf8\x06'%\xf3\x9c\x82\xd59N>\ -\xbcy\x7f\x82MN*\xcb\x09\xd0\xb8\x1c\xa7\xb7xr\ -r\xda\x1a\x93\xcesr\x98\xe5t\xaa\xdf\xa9w\xa1\xc9\ -)\x8f\xb8\x92!dC\xc5O\xde\xab\xf7\xa6\xc9\xc9\xe7\ -9i4\xd9\xaf^\x09\xa6\xf7M+@\x99\xe7\xe4\x94\ -\xda\x1f\xda\x9d8\xa9\xfb\xd8\xee\xe64\xd871\xfc\xd3\ -&F8\xf4\xcc0h\x8cqMI\x5c\x89\xc9v\x9d\ -\x03Z\x0b\xda\xe4m\xd1\xbf\xf1'\x0d~\x1a*~\x19\ -\xdd\xab`\xad\xf7y\xfd\xd3\xd3\xe2\xe8\xf2ZC\x8by\ -\xfd\xf3\xd3\xe4\x14\x0e\xa5\x7f\x03YN\x06\xe1)c2\ -\x98\xe5\xe4\xb5\x0e\xf9+\x15\x1d\x9c\x8c\xc9p\x82,\x13\ -\xa0i\x8bM&.k\xd8\xdds!7J1\x07\xfb\ -\x0fq\xed\x90\x82N@A;\x9c\xa0\x15L\xb0\x15\xa6\ -'h\xcd\xaa\xa7\x22\xc4\xd3\xc5`\x93\x89n\x5c\xebx\ -,B \x9f\x04\x91\xa9\xa1C\xfd\xb7#\x13\x9aq\x09\ -_\x0fL8\x000\x0e\x9e\x07\x0c\x1c\x02\x98\x14\xe4\x90\ -B\x1c\xbe\x1a\x16g\x9f\x0a\x0b\x06|\x8b\xbe\xc9\xc4?\ -\x0f\x16\xf5$X,'S\xc4D;^\xa2\x19-\x91\ -\x07&\x7fq\xae!\x93\xcf;-)\x9d\xda\x7fw.\ -q2yN\xd6\xe1\xfe[s\x89\x93\xdb\xb1\x09\xd2n\ -\xff\x9d\xb9\xc4i\xc72\x01`\xf6\xde\x98K\x9c\x02d\ -9\xa1\x04\xb5\xff\xbe\x5c\xe2\x94G\x5c\x83R\xfbo\xcb\ -%Ny\xc4\x8du{\xee\xca59\xe5\x10\x87'\xcc\ -\xb1\x102\xd3\xe3\xe5\x15\xdd\xf8\xa8jr\xda\x9aP\xea\ -\x89\xf2?\xf7\x14\x0eB\xef'\xfa%\xa4\x96\x1e\xec\x00\ -\x14P\xdek\x01N\xc5\xcc\x09g\x9c,3\xde\xb0\xee\ -(\xc9q\x03\x81\xb3\xc6\xdb\x98\xd5\xa0\xa9\x1d\x80\xe7\xd4\ -\xc0;.\xa0\xb6D\xe0\x9aD\xe0\x86\x94\xa5\xa4ev\ -U\x92\xba\x89\xed\x02\x8f\xa3\x1c\xce\x97^]\xf0\x87\xe0\ -Ha\xe2]\x9a\xeaNn\xebb\x0de\xf9\xa8Z\xde\ -\xc3I\xa1\x09G\x91\xd5f\xcbw\x1eR\x83\xb5\xd6\xdd\ -\xc7]H4\x0e4\x0e\x86A\x04\xe3\x11\x91\x92(P\ -\x81A\xadJ&\xe9\x0d\xcerZ\xf0a\x8f\xfc\xc8\xe4\ -\xfe\xc3\xe0WHw\xaf\x05u\x83e\x8e\x92\x06 :\ -\x13I\x00{\x02 \xe5\x08T\xca;\xc7\x08U\xf9\x13\ -\xce;\xf9\x90w\x9eQ\x8d9\x08\x8c3Q\x18\xf8\x8a\ -R)\x05J\xa5\xd8\x07\xa5@\xa9\x14{\xaf\x14\xea\xdb\ -0\xdf*Y)\xa5=&\xcf\xba\xa9\x1b\x13\xeaCC\ -\xa3:7\xa6\x12R\xbe\x0c\x962\x03`(\xd8\xd40\ -h\x1d8\x0b\xfe\x017)\xb4\x96\x1em\x93\x16\x03\xab\ -\xa24\x89\x0aN\xc8H=iQ\x8d\x95`\xea\x1d\x80\ -\xaf\xf7\x1e\xf98+|\x00\xa7\x1d\xe7K\xcd\x80\x0e`\ -\x80\xf3\x9c\xd3\x12\xac-\xf9\xf9`\x022\xcdxtR\ -G\x9a\x03\x85\xc8\xe3\x91\x81\xd2\xa8\x90\x81\xd7\x1e\x14\xf1\ -b\x9a\x0fVY\xa6\xa9\xe0H\x16\x90\x9c\x96\xda(\x87\ -eN\x0b\x1a\x8e4&jJ\xd6d<\xe1|\xc2!\ -r3\x16\x0c\x12-f\xa4\x02\xe59\xf3S\x03\xd3\xb6\ -rMH\xba\xab+\xb9|W:^M\xb6MS\xd8\ -1I\xa5\xb0^j\x87\x18(\xeey\xb5\xfc\xbd {\ -Y\x14\xf7\xe9\xf2E=\xd5\xd1eS\x90\xf7\x1fUU\ -`\xd7F\xafm\x8f\xe3\x9f|\xa8\x13\xff\xb9\x9c-\x9a\ -\xd4\xcb\x19\xbfs\x9a\xd1\xafc]\xd1\xa6cz\xdb\xbf\ -Z\x8d\xef\x1a\xbd25:\x5c\xaa)\xc0\xc8\xfa\x07m\ -Uk\xa7\x9b\xe1\xf9\xdc\x9c\x15J\x99\xe6\xbcH\xdf\xf9\ -\xd5\x89\xe9rb\xe3b\xa2\xf2-\xda\x1d\xd3\xd2<`\ -\xe5\x91Z\xdf\xf4\xe8\xb7\xf2=\xd2)=NuhU\ -\xc5/Y\x1f\x08\xe8}\xb0\xa5\x0b\xac2C\x05\xc2\xb3\ -\xc1\xc6o\x81\x81>\xd6\xf9\xf2k\xde\x8bJ\xc8\x0b\x08\ -\x0e\x13\xd7|\x08_\xb2\x9e-\xfe\x1c\xbeO~\xa0\x1b\ -`\x1d!\xad\xaf[\x11\xa9\x0a\x83\xad>\xcd\xdaH@\ -\x929 \xbb\xf4A\xc0\x9e\xf7\xf4\x93*p\x81\xb6\x91\ -Bt\xcd\x99\x98\xb2\x5c\xc6\xe6\x0f\xdc\x82\x1c(\xb7g\ -\xef\xc9\xec\xee\xfdi\xde\x99~\xe9\x112\xcf\x97\x12\xdb\ -R:\xecJ\x99\xf7\xdc\xc1\x09\xad5I\x13\xa5\xd4\xa5\ -\x94>\xb8H\xb1\xf4\x9331ec\x19Ii\xb8\xc5\ -\xfd\xc2\x91V\x8do+\xa5iK\xe9\xe5~]\x1ad\ -\x02j\xc7RzI2\x99(\xa5aIX\xee\x92\xc2\ -?9\x13S6\x96\xb1\x94\xdc\x22\xea2\xfa\xe7\xc0\xec\ -\x9e-\xe5\x0e\xc9\x5c[2\x90P\x13\xad\xdd\xe5I\x9b\ -\x10\xa40\x83\xe8\x9f{\x81\xb5\xc3U\x88\x05\x11\x03U\ -l\x0d\xb0\x03T\x9b@mT\xb4h\xef\xef\xb1`/\ -o\x89\xd8\xea\xb7\x95\xad$\x1f]\xd0q*]\xa5\xee\ -^\xa3\xce\x5c\xa1\xee^\x9f\xce_\x9d\xde\x0f\xab\x0f`\ -\xbb\x9b\x80tup\xebu\xe9n/i,\xed+\xd2\ -\x8d\xeb\xd1h\x1c\xb5|\x91v\xcef\x1cV\xf2f]W\xd7\ -u\x87\xc9Uv\x1dL\xe9Y\xa2\xe22\x96\x14e\xef\ -F9\x1f5\xad\xcb\x0ag\x00=x\xf7\x14K\xd9?\ -\xff\x1d\x82;\xda\xb1\x84dW\x9c\xfc\xca\x94V\xb0\xfc\ -\xaccd\xf2{\x18\x0d;\xce\xa5A\x89h\xe2n\xe0\ -\x05h\xaf\xa4S\xbc\xf6Ja\x8dF\x055\xaa\x03\x01\ -\x0e\x83V\x03\xb0\x02L\xf0\x80=\xeb\x85\xf7>(\x1c\ -(+\xbc\xb2Zqk\x87Bk4^\x0f\x10\x045\ -Ep=O\xfdx\xc3/i\x90\xb0@g\x10R\xdf\ -\x89v\xbf\x85\x08\xa0U\x9d\x0a \x851V\x87\x1aG\ -\x00\x13w\xf8\xa6\xd17\x80\xe2\x0c\xe8\xda(A\xa2@\ -m\x9cI\xd2l\x95\xfbK\xcb\xa4\xc1:g\x0fp\x8c\ -|\xb2\xcb\xfbwz\xba\xc7\x9d\x12\x15\xb4\x965\xa9\xdd\ -\x96em\xcb\x96\xcaw\xed[w\xcd[\xd7\xd64)\ -\x07\x9e\x96/=\xd0\xf7\x8bWP-B\xac\x92]\xcc\ -\x08Gt\xe5Jv\x9f4r`\x85I\xeb\xd6\x1e\xcf\ -\xdaZ\xc0\xbd\xf9\x06\xde\xfe\xff\xee\xd2\xb9\xc7\x8f\xc9\xe6\ -Gg\xfd\x1abpy\xbf\xe6}/\x82\x0d\xaa\x84\xbe\ -\x87X\xde\x16c\xef\x16\x04J\xf4\xda1\xd1{#=\ -\xf2a\xc6j\x00`R`\x15\x05U%\xa9E\xb0\x22\ -H'S);)\x1d\x8c\x8c|\x95B\x17,\xd1\x8c\ -\xd0\x5cx\xaf\xfa\xeaws )\xd7~\x1d\x89AF\ -l8\xa8\xe7\xf5\xbf\x00\x1d$M\x88\x06W\x00\x00\ -\x00\x00\x03\xee\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x00\x14\x00\x00\x00\x14\x08\x03\x00\x00\x00\xbaW\xed?\ -\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\ -\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\ -\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x01\x8f\ -PLTE\xff\xff\xff\xdd\xe1\xe4\x97\xa2\xad`p\x80\ -FYlCWjWhy\x86\x92\x9f\xcc\xd1\xd7\xfc\ -\xfc\xfd\xf8\xf9\xfa\xa8\xb1\xbaN`r0EZ3H\ -]Qe\xd8\xdc\ -\xe0CVi\xb3\xc8\xd8\xc7\xdb\xebUj~A>\xd1\x08\x22\xa6\x96\x94\x9c\x92\x0avR\ -Z\xbaP\x86\xb1\x9a\x8b\xba\xa1\xbajR&oV6\ -\xc4G\xa29Z&\xb9\xae\xde\x81a\xd1y\xbcZ\xf9\ -\x91P\x7f\x16\x14\x16\x15\x97\x94\x06\x85\xc7\xa4d\xd9\x0b\ -\x95\xc1D\xcb+l\xdd|\x83#*S\xb3\xab\xaak\ -\xe0\xa2\x0cv\x8a~!\x91 {#kk\xea\x22\x19\ -\xd0\x01P\xb4\xbe\x01C\xb4\xaaQ\xa8\x09C\x90\xa1\xb9\ -\xae\x05\x00\xcf\xbe>\xfb#\xb9\xea4\x00\x00\x00%t\ -EXtdate:create\x002\ -023-03-08T17:27:\ -26+00:00\x9e\x98\xa5\x85\x00\x00\x00%\ -tEXtdate:modify\x00\ -2023-03-08T17:27\ -:26+00:00\xef\xc5\x1d9\x00\x00\x00\ -(tEXtdate:timest\ -amp\x002023-03-09T0\ -6:20:50+00:00\xdd\xe8\xb2\ -\xde\x00\x00\x00\x00IEND\xaeB`\x82\ " qt_resource_name = b"\ @@ -4861,6 +1861,15 @@ qt_resource_name = b"\ \x00j\xc4c\ \x00d\ \x00e\x00m\x00o\x00s\ +\x00\x04\ +\x00\x06\xc4\xee\ +\x00i\ +\x001\x008\x00n\ +\x00\x14\ +\x09u:=\ +\x00d\ +\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00v\x00i\x00e\x00w\x00e\x00r\x00_\x00d\x00e\ +\x00.\x00q\x00m\ \x00\x0e\ \x00\x97\xbf\xa2\ \x00d\ @@ -4873,80 +1882,14 @@ qt_resource_name = b"\ \x0dJ\x02g\ \x00p\ \x00a\x00s\x00t\x00e\x00@\x002\x00x\x00.\x00p\x00n\x00g\ -\x00\x11\ -\x0e\xab3G\ -\x00z\ -\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\ -\ -\x00\x0c\ -\x02\xb1%j\ -\x00g\ -\x00o\x00-\x00n\x00e\x00x\x00t\x00.\x00s\x00v\x00g\x00z\ -\x00\x0f\ -\x0a\x12\xe5*\ -\x00h\ -\x00e\x00l\x00p\x00-\x00a\x00b\x00o\x00u\x00t\x00.\x00s\x00v\x00g\x00z\ -\x00\x10\ -\x0a~p\x07\ -\x00m\ -\x00a\x00g\x00n\x00i\x00f\x00i\x00e\x00r\x00@\x002\x00x\x00.\x00p\x00n\x00g\ -\x00\x1c\ -\x02\x8c\xf4'\ -\x00g\ -\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00-\ -\x00p\x00a\x00g\x00e\x00@\x002\x00x\x00.\x00p\x00n\x00g\ -\x00\x14\ -\x03\xba\xd1'\ -\x00z\ -\x00o\x00o\x00m\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00@\x002\x00x\x00.\ -\x00p\x00n\x00g\ -\x00\x13\ -\x05\x93\x9e\x87\ -\x00g\ -\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00@\x002\x00x\x00.\x00p\ -\x00n\x00g\ \x00\x0f\ \x0f\x91\x8b\xc7\ \x00z\ \x00o\x00o\x00m\x00-\x00o\x00u\x00t\x00@\x002\x00x\x00.\x00p\x00n\x00g\ -\x00\x14\ -\x04jv\xe7\ -\x00z\ -\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00b\x00e\x00s\x00t\x00@\x002\x00x\x00.\ -\x00p\x00n\x00g\ -\x00\x12\ -\x01\xfcL\x07\ -\x00z\ -\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00w\x00i\x00d\x00t\x00h\x00.\x00p\x00n\ -\x00g\ -\x00\x14\ -\x06\x10\x9a\xe7\ -\x00g\ -\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00.\ -\x00p\x00n\x00g\ -\x00\x08\ -\x06\xc1Y\x87\ -\x00o\ -\x00p\x00e\x00n\x00.\x00p\x00n\x00g\ -\x00\x18\ -\x0d\xd6\xb5G\ -\x00g\ -\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00-\x00p\x00a\x00g\x00e\ -\x00@\x002\x00x\x00.\x00p\x00n\x00g\ \x00\x0b\ \x03\x03\x9bG\ \x00z\ \x00o\x00o\x00m\x00-\x00i\x00n\x00.\x00p\x00n\x00g\ -\x00\x14\ -\x05\xda\x8d\xc7\ -\x00z\ -\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00@\x002\x00x\x00.\ -\x00p\x00n\x00g\ -\x00\x11\ -\x0c2UG\ -\x00z\ -\x00o\x00o\x00m\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00.\x00p\x00n\x00g\ -\ \x00\x0a\ \x03\x96\x0eG\ \x00c\ @@ -4955,32 +1898,10 @@ qt_resource_name = b"\ \x0f[7G\ \x00z\ \x00o\x00o\x00m\x00-\x00i\x00n\x00@\x002\x00x\x00.\x00p\x00n\x00g\ -\x00\x10\ -\x06O7\xc7\ -\x00g\ -\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00.\x00p\x00n\x00g\ -\x00\x0b\ -\x03w\x8f\x07\ -\x00o\ -\x00p\x00e\x00n\x00@\x002\x00x\x00.\x00p\x00n\x00g\ \x00\x0c\ \x06\xeb\x97\xe7\ \x00z\ \x00o\x00o\x00m\x00-\x00o\x00u\x00t\x00.\x00p\x00n\x00g\ -\x00\x15\ -\x0f!\xb3\x87\ -\x00g\ -\x00o\x00-\x00n\x00e\x00x\x00t\x00-\x00v\x00i\x00e\x00w\x00-\x00p\x00a\x00g\x00e\ -\x00.\x00p\x00n\x00g\ -\x00\x09\ -\x00W\xb8g\ -\x00p\ -\x00r\x00i\x00n\x00t\x00.\x00p\x00n\x00g\ -\x00\x15\ -\x02/h'\ -\x00z\ -\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00w\x00i\x00d\x00t\x00h\x00@\x002\x00x\ -\x00.\x00p\x00n\x00g\ \x00\x0e\ \x0d\x1d\xfd'\ \x00q\ @@ -4989,37 +1910,14 @@ qt_resource_name = b"\ \x05R\xbf'\ \x00q\ \x00t\x00-\x00l\x00o\x00g\x00o\x00.\x00p\x00n\x00g\ -\x00\x10\ -\x03g\xb5*\ -\x00g\ -\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00.\x00s\x00v\x00g\x00z\ \x00\x08\ \x06|Z\x07\ \x00c\ \x00o\x00p\x00y\x00.\x00p\x00n\x00g\ -\x00\x19\ -\x0d\x16F'\ -\x00g\ -\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00-\ -\x00p\x00a\x00g\x00e\x00.\x00p\x00n\x00g\ \x00\x0b\ \x03O\x98\xe7\ \x00c\ \x00o\x00p\x00y\x00@\x002\x00x\x00.\x00p\x00n\x00g\ -\x00\x17\ -\x0fA\x96\xe7\ -\x00g\ -\x00o\x00-\x00p\x00r\x00e\x00v\x00i\x00o\x00u\x00s\x00-\x00v\x00i\x00e\x00w\x00@\ -\x002\x00x\x00.\x00p\x00n\x00g\ -\x00\x11\ -\x09\x02(G\ -\x00z\ -\x00o\x00o\x00m\x00-\x00f\x00i\x00t\x00-\x00b\x00e\x00s\x00t\x00.\x00p\x00n\x00g\ -\ -\x00\x0b\ -\x07tw\xe7\ -\x00p\ -\x00r\x00i\x00n\x00t\x002\x00x\x00.\x00p\x00n\x00g\ \x00\x09\ \x0a\xa8\xbaG\ \x00p\ @@ -5028,102 +1926,45 @@ qt_resource_name = b"\ \x0a\xc7W\x87\ \x00c\ \x00u\x00t\x00.\x00p\x00n\x00g\ -\x00\x19\ -\x08\xc3Z\xea\ -\x00d\ -\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00o\x00p\x00e\x00n\x00-\x00r\x00e\x00c\ -\x00e\x00n\x00t\x00.\x00s\x00v\x00g\x00z\ -\x00\x0d\ -\x099\xc9\x07\ -\x00m\ -\x00a\x00g\x00n\x00i\x00f\x00i\x00e\x00r\x00.\x00p\x00n\x00g\ " qt_resource_struct = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x10\x00\x02\x00\x00\x00\x01\x00\x00\x00\x11\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x10\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00L\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x002\x00\x02\x00\x00\x00&\x00\x00\x00\x04\ +\x00\x00\x00n\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x05\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\xb9n\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x01\xdc\x00\x00\x00\x00\x00\x01\x00\x00m\xfb\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x03\xd6\x00\x00\x00\x00\x00\x01\x00\x00\xbc\x18\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x00\xf2\x00\x00\x00\x00\x00\x01\x00\x00H^\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x0e\x98\ -\x00\x00\x01\x88\xd8-C\xa1\ -\x00\x00\x02\x80\x00\x00\x00\x00\x00\x01\x00\x00\x86\xa7\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x04\xb8\x00\x00\x00\x00\x00\x01\x00\x00\xf0\x17\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x04D\x00\x00\x00\x00\x00\x01\x00\x00\xd08\ -\x00\x00\x01\x88\xd8-C\xa1\ -\x00\x00\x03T\x00\x00\x00\x00\x00\x01\x00\x00\xadX\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x02\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x95\xf2\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x010\x00\x00\x00\x00\x00\x01\x00\x00Pu\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x01\xae\x00\x00\x00\x00\x00\x01\x00\x00g[\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x04(\x00\x00\x00\x00\x00\x01\x00\x00\xcai\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x01^\x00\x00\x00\x00\x00\x01\x00\x00Xz\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x02\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x8ac\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x02\x06\x00\x00\x00\x00\x00\x01\x00\x00q\x88\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x03.\x00\x00\x00\x00\x00\x01\x00\x00\xa9v\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x04j\x00\x00\x00\x00\x00\x01\x00\x00\xe6v\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x024\x00\x00\x00\x00\x00\x01\x00\x00u~\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x03p\x00\x00\x00\x00\x00\x01\x00\x00\xb1U\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x050\x00\x00\x00\x00\x00\x01\x00\x01\x04L\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x05x\x00\x00\x00\x00\x00\x01\x00\x01\x13\x93\ -\x00\x00\x01\x88\xd8-C\xa1\ -\x00\x00\x05\x08\x00\x00\x00\x00\x00\x01\x00\x01\x00\x1d\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x05\xb0\x00\x00\x00\x00\x00\x01\x00\x01%\xc7\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00#\xfd\ -\x00\x00\x01\x88\xd8-C\xa1\ -\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00?\x91\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x05L\x00\x00\x00\x00\x00\x01\x00\x01\x07\xf3\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x05d\x00\x00\x00\x00\x00\x01\x00\x01\x0ed\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x02\xca\x00\x00\x00\x00\x00\x01\x00\x00\x92<\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x00\xeb\xb4\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x04\x06\x00\x00\x00\x00\x00\x01\x00\x00\xc2\xd8\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x02J\x00\x00\x00\x00\x00\x01\x00\x00}\x9b\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x00b\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xe2\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x03\x8e\x00\x00\x00\x00\x00\x01\x00\x00\xb5\x05\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x04\xd4\x00\x00\x00\x00\x00\x01\x00\x00\xf8\xc6\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x03\x0c\x00\x00\x00\x00\x00\x01\x00\x00\xa1>\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ -\x00\x00\x01\x8a\x00\x00\x00\x00\x00\x01\x00\x00_\xa4\ -\x00\x00\x01\x87\xdc\x8a\xff\x8e\ +\x00\x00\x00\xc2\x00\x00\x00\x00\x00\x01\x00\x00/\xe4\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x01\x8c\x00\x00\x00\x00\x00\x01\x00\x00]r\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x003\xa0\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x01Z\x00\x00\x00\x00\x00\x01\x00\x00Re\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x01v\x00\x00\x00\x00\x00\x01\x00\x00X4\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x01\x1a\x00\x00\x00\x00\x00\x01\x00\x00G$\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x01\xa8\x00\x00\x00\x00\x00\x01\x00\x00f!\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00l\x92\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x018\x00\x00\x00\x00\x00\x01\x00\x00J\xd4\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x00\x1dK\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x01\x00\x00>\xec\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x00\x9e\x00\x00\x00\x00\x00\x01\x00\x00(-\ +\x00\x00\x01\x9c\xdc\x98\xa9\xea\ +\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9dIe\xbd3\ " def qInitResources(): diff --git a/examples/demos/documentviewer/recentfilemenu.py b/examples/demos/documentviewer/recentfilemenu.py index be5ffaf4..a7dd3e63 100644 --- a/examples/demos/documentviewer/recentfilemenu.py +++ b/examples/demos/documentviewer/recentfilemenu.py @@ -19,10 +19,11 @@ class RecentFileMenu(QMenu): @Slot() def updateList(self): for a in self.actions(): + self.removeAction(a) del a if not self._recentFiles: - self.addAction("") + self.addAction(self.tr("")) return for fileName in self._recentFiles.recentFiles(): diff --git a/examples/demos/documentviewer/recentfiles.py b/examples/demos/documentviewer/recentfiles.py index 3f6e5e7a..afecd546 100644 --- a/examples/demos/documentviewer/recentfiles.py +++ b/examples/demos/documentviewer/recentfiles.py @@ -58,10 +58,6 @@ class RecentFiles(QObject): def addFile(self, fileName): self._addFile(fileName, EmitPolicy.EmitWhenChanged) - def removeFile(self, fileName): - idx = self._files.find(fileName) - self._removeFile(idx, RemoveReason.Other) - @Slot() def clear(self): if self.isEmpty(): @@ -81,15 +77,15 @@ class RecentFiles(QObject): while i < len(self._files): file = self._files[i] if not testFileAccess(file): - self._removeFile(file, RemoveReason.Other) + self._removeFile(i, RemoveReason.Other) elif file == fileName: - self._removeFile(file, RemoveReason.Duplicate) + self._removeFile(i, RemoveReason.Duplicate) else: i += 1 # Cut tail while len(self._files) > self._maxFiles: - self.removeFile((len(self._files) - 1), RemoveReason.Other) + self._removeFile(len(self._files) - 1, RemoveReason.Other) self._files.insert(0, fileName) @@ -119,10 +115,7 @@ class RecentFiles(QObject): if len(self._files) != c: self.countChanged.emit(len(self._files)) - def _removeFile(self, p, reason): - index = p - if isinstance(p, str): - index = self._files.index(p) if p in self._files else -1 + def _removeFile(self, index, reason): if index < 0 or index >= len(self._files): return del self._files[index] diff --git a/examples/demos/documentviewer/translator.py b/examples/demos/documentviewer/translator.py new file mode 100644 index 00000000..31675f17 --- /dev/null +++ b/examples/demos/documentviewer/translator.py @@ -0,0 +1,37 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from PySide6.QtCore import QLocale, QTranslator +from PySide6.QtWidgets import QApplication # noqa: F401 + + +class Translator: + def __init__(self): + self._translator = QTranslator() + self._baseName = "" + self._trLocale = QLocale() + + def setBaseName(self, baseName): + self._baseName = baseName + + def setLanguage(self, lang): + self._trLocale = QLocale(lang) + + def install(self): + if not self._baseName: + print("The basename of the translation is not set. Ignoring.", file=sys.stderr) + return + + if not self._translator.isEmpty(): + qApp.removeTranslator(self._translator) # noqa: F821 + + if (self._translator.load(self._trLocale, self._baseName, "_", ":/i18n/") + and qApp.installTranslator(self._translator)): # noqa: F821 + print("Loaded translation", self._translator.filePath(), file=sys.stderr) + else: + if self._trLocale.language() != QLocale.Language.English: + msg = (f"Failed to load translation {self._baseName} for locale " + f"{self._trLocale.name()}. Falling back to English translation") + print(msg, file=sys.stderr) + self.setLanguage(QLocale.Language.English) diff --git a/examples/demos/documentviewer/txtviewer/txtviewer.py b/examples/demos/documentviewer/txtviewer/txtviewer.py index 31754eae..9670eac5 100644 --- a/examples/demos/documentviewer/txtviewer/txtviewer.py +++ b/examples/demos/documentviewer/txtviewer/txtviewer.py @@ -13,8 +13,33 @@ from abstractviewer import AbstractViewer class TxtViewer(AbstractViewer): def __init__(self): super().__init__() + self._textEdit = None self.uiInitialized.connect(self.setupTxtUi) + cutIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, + QIcon(":/demos/documentviewer/images/cut.png")) + self._cutAct = QAction(self) + self._cutAct.setText(self.tr("Cut")) + self._cutAct.setIcon(cutIcon) + self._cutAct.setShortcuts(QKeySequence.StandardKey.Cut) + self._cutAct.setStatusTip(self.tr("Cut the current selection's contents to the clipboard")) + + copyIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, + QIcon(":/demos/documentviewer/images/copy.png")) + self._copyAct = QAction(self) + self._copyAct.setText(self.tr("Copy")) + self._copyAct.setIcon(copyIcon) + self._copyAct.setShortcuts(QKeySequence.StandardKey.Copy) + self._copyAct.setStatusTip(self.tr("Copy the current selection's contents to the clipboard")) # noqa: E501 + + pasteIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, + QIcon(":/demos/documentviewer/images/paste.png")) + self._pasteAct = QAction(self) + self._pasteAct.setText(self.tr("Paste")) + self._pasteAct.setIcon(pasteIcon) + self._pasteAct.setShortcuts(QKeySequence.StandardKey.Paste) + self._pasteAct.setStatusTip(self.tr("Paste the clipboard's contents into the current selection")) # noqa: E501 + def init(self, file, parent, mainWindow): self._textEdit = QPlainTextEdit(parent) super().init(file, self._textEdit, mainWindow) @@ -22,47 +47,45 @@ class TxtViewer(AbstractViewer): def viewerName(self): return "TxtViewer" + def cleanup(self): + del self._textEdit + self._textEdit = None + super().cleanup() + def supportedMimeTypes(self): return ["text/plain"] + def retranslate(self): + if not self._toolBars: + return + self._menus[0].setTitle(self.tr("Edit")) + self._toolBars[0].setWindowTitle(self.tr("Edit")) + self._cutAct.setText(self.tr("C&ut")) + self._copyAct.setText(self.tr("&Copy")) + self._pasteAct.setText(self.tr("&Paste")) + @Slot() def setupTxtUi(self): - editMenu = self.addMenu("Edit") - editToolBar = self.addToolBar("Edit") - cutIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, - QIcon(":/demos/documentviewer/images/cut.png")) - cutAct = QAction(cutIcon, "Cut", self) - cutAct.setShortcuts(QKeySequence.StandardKey.Cut) - cutAct.setStatusTip("Cut the current selection's contents to the clipboard") - cutAct.triggered.connect(self._textEdit.cut) - editMenu.addAction(cutAct) - editToolBar.addAction(cutAct) - - copyIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, - QIcon(":/demos/documentviewer/images/copy.png")) - copyAct = QAction(copyIcon, "Copy", self) - copyAct.setShortcuts(QKeySequence.StandardKey.Copy) - copyAct.setStatusTip("Copy the current selection's contents to the clipboard") - copyAct.triggered.connect(self._textEdit.copy) - editMenu.addAction(copyAct) - editToolBar.addAction(copyAct) - - pasteIcon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, - QIcon(":/demos/documentviewer/images/paste.png")) - pasteAct = QAction(pasteIcon, "Paste", self) - pasteAct.setShortcuts(QKeySequence.StandardKey.Paste) - pasteAct.setStatusTip("Paste the clipboard's contents into the current selection") - pasteAct.triggered.connect(self._textEdit.paste) - editMenu.addAction(pasteAct) - editToolBar.addAction(pasteAct) + editMenu = self.addMenu() + editToolBar = self.addToolBar() + editMenu.addAction(self._cutAct) + editToolBar.addAction(self._cutAct) + editMenu.addAction(self._copyAct) + editToolBar.addAction(self._copyAct) + editMenu.addAction(self._pasteAct) + editToolBar.addAction(self._pasteAct) self.menuBar().addSeparator() - cutAct.setEnabled(False) - copyAct.setEnabled(False) - self._textEdit.copyAvailable.connect(cutAct.setEnabled) - self._textEdit.copyAvailable.connect(copyAct.setEnabled) + self._cutAct.setEnabled(False) + self._copyAct.setEnabled(False) + self._cutAct.triggered.connect(self._textEdit.cut) + self._copyAct.triggered.connect(self._textEdit.copy) + self._pasteAct.triggered.connect(self._textEdit.paste) + self._textEdit.copyAvailable.connect(self._cutAct.setEnabled) + self._textEdit.copyAvailable.connect(self._copyAct.setEnabled) + self.retranslate() self.openFile() self._textEdit.textChanged.connect(self._textChanged) @@ -91,7 +114,8 @@ class TxtViewer(AbstractViewer): if not self._file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text): err = self._file.errorString() - self.statusMessage(f"Cannot read file {file_name}:\n{err}.", type) + message = self.tr("Cannot read file {}:\n{}.").format(file_name, err) + self.statusMessage(message, type) return in_str = QTextStream(self._file) @@ -103,7 +127,7 @@ class TxtViewer(AbstractViewer): self._textEdit.setPlainText(in_str.readAll()) QGuiApplication.restoreOverrideCursor() - self.statusMessage(f"File {file_name} loaded.", type) + self.statusMessage(self.tr("File {} loaded.").format(file_name), type) self.maybeEnablePrinting() def hasContent(self): @@ -124,14 +148,14 @@ class TxtViewer(AbstractViewer): out << self._textEdit.toPlainText() else: error = file.errorString() - errorMessage = f"Cannot open file {file_name} for writing:\n{error}." + errorMessage = self.tr("Cannot open file {} for writing:\n{}.").format(file_name, error) QGuiApplication.restoreOverrideCursor() if errorMessage: self.statusMessage(errorMessage) return False - self.statusMessage(f"File {file_name} saved") + self.statusMessage(self.tr("File {} saved").format(file_name)) return True def saveDocumentAs(self): diff --git a/examples/demos/documentviewer/ui_mainwindow.py b/examples/demos/documentviewer/ui_mainwindow.py index 3c410047..95f9ce9c 100644 --- a/examples/demos/documentviewer/ui_mainwindow.py +++ b/examples/demos/documentviewer/ui_mainwindow.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'mainwindow.ui' ## -## Created by: Qt User Interface Compiler version 6.8.0 +## Created by: Qt User Interface Compiler version 6.11.0 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -31,39 +31,24 @@ class Ui_MainWindow(object): MainWindow.setWindowIcon(icon) self.actionOpen = QAction(MainWindow) self.actionOpen.setObjectName(u"actionOpen") - icon1 = QIcon() - icon1.addFile(u":/demos/documentviewer/images/open.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen)) self.actionOpen.setIcon(icon1) self.actionAbout = QAction(MainWindow) self.actionAbout.setObjectName(u"actionAbout") - icon2 = QIcon() - iconThemeName = u"help-about" - if QIcon.hasThemeIcon(iconThemeName): - icon2 = QIcon.fromTheme(iconThemeName) - else: - icon2.addFile(u":/demos/documentviewer/images/help-about.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - + icon2 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)) self.actionAbout.setIcon(icon2) self.actionForward = QAction(MainWindow) self.actionForward.setObjectName(u"actionForward") - icon3 = QIcon() - icon3.addFile(u":/demos/documentviewer/images/go-next.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + icon3 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.GoNext)) self.actionForward.setIcon(icon3) self.actionBack = QAction(MainWindow) self.actionBack.setObjectName(u"actionBack") - icon4 = QIcon() - icon4.addFile(u":/demos/documentviewer/images/go-previous.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + icon4 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.GoPrevious)) self.actionBack.setIcon(icon4) self.actionPrint = QAction(MainWindow) self.actionPrint.setObjectName(u"actionPrint") self.actionPrint.setEnabled(False) - icon5 = QIcon() - iconThemeName = u"document-print" - if QIcon.hasThemeIcon(iconThemeName): - icon5 = QIcon.fromTheme(iconThemeName) - else: - icon5.addFile(u":/demos/documentviewer/images/print2x.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) - + icon5 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentPrint)) self.actionPrint.setIcon(icon5) self.actionAboutQt = QAction(MainWindow) self.actionAboutQt.setObjectName(u"actionAboutQt") @@ -73,13 +58,16 @@ class Ui_MainWindow(object): self.actionAboutQt.setIcon(icon6) self.actionRecent = QAction(MainWindow) self.actionRecent.setObjectName(u"actionRecent") - icon7 = QIcon() - icon7.addFile(u":/demos/documentviewer/images/document-open-recent.svgz", QSize(), QIcon.Mode.Normal, QIcon.State.Off) + icon7 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpenRecent)) self.actionRecent.setIcon(icon7) - self.actionQuit = QAction(MainWindow) - self.actionQuit.setObjectName(u"actionQuit") + self.actionExit = QAction(MainWindow) + self.actionExit.setObjectName(u"actionExit") icon8 = QIcon(QIcon.fromTheme(u"application-exit")) - self.actionQuit.setIcon(icon8) + self.actionExit.setIcon(icon8) + self.actionEnglish = QAction(MainWindow) + self.actionEnglish.setObjectName(u"actionEnglish") + self.actionDeutsch = QAction(MainWindow) + self.actionDeutsch.setObjectName(u"actionDeutsch") self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") self.centralwidget.setEnabled(True) @@ -113,7 +101,7 @@ class Ui_MainWindow(object): self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents") - self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 798, 472)) + self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 798, 457)) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.splitter.addWidget(self.scrollArea) @@ -125,11 +113,13 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 983, 26)) + self.menubar.setGeometry(QRect(0, 0, 983, 24)) self.qtFileMenu = QMenu(self.menubar) self.qtFileMenu.setObjectName(u"qtFileMenu") self.menuHelp = QMenu(self.menubar) self.menuHelp.setObjectName(u"menuHelp") + self.menuLanguage = QMenu(self.menuHelp) + self.menuLanguage.setObjectName(u"menuLanguage") MainWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(MainWindow) self.statusbar.setObjectName(u"statusbar") @@ -143,9 +133,12 @@ class Ui_MainWindow(object): self.qtFileMenu.addAction(self.actionOpen) self.qtFileMenu.addAction(self.actionRecent) self.qtFileMenu.addAction(self.actionPrint) - self.qtFileMenu.addAction(self.actionQuit) + self.qtFileMenu.addAction(self.actionExit) self.menuHelp.addAction(self.actionAbout) self.menuHelp.addAction(self.actionAboutQt) + self.menuHelp.addAction(self.menuLanguage.menuAction()) + self.menuLanguage.addAction(self.actionEnglish) + self.menuLanguage.addAction(self.actionDeutsch) self.mainToolBar.addAction(self.actionOpen) self.mainToolBar.addAction(self.actionRecent) self.mainToolBar.addAction(self.actionPrint) @@ -155,7 +148,7 @@ class Ui_MainWindow(object): self.mainToolBar.addSeparator() self.retranslateUi(MainWindow) - self.actionQuit.triggered.connect(MainWindow.close) + self.actionExit.triggered.connect(MainWindow.close) self.tabWidget.setCurrentIndex(0) @@ -165,32 +158,32 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Document Viewer Demo", None)) - self.actionOpen.setText(QCoreApplication.translate("MainWindow", u"Open", None)) + self.actionOpen.setText(QCoreApplication.translate("MainWindow", u"&Open", None)) #if QT_CONFIG(shortcut) self.actionOpen.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+O", None)) #endif // QT_CONFIG(shortcut) - self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"about documentviewer", None)) + self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About Document Viewer", None)) #if QT_CONFIG(tooltip) self.actionAbout.setToolTip(QCoreApplication.translate("MainWindow", u"Show information about the Document Viewer deomo.", None)) #endif // QT_CONFIG(tooltip) #if QT_CONFIG(shortcut) self.actionAbout.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+H", None)) #endif // QT_CONFIG(shortcut) - self.actionForward.setText(QCoreApplication.translate("MainWindow", u"actionForward", None)) + self.actionForward.setText(QCoreApplication.translate("MainWindow", u"Forward", None)) #if QT_CONFIG(tooltip) self.actionForward.setToolTip(QCoreApplication.translate("MainWindow", u"One step forward", None)) #endif // QT_CONFIG(tooltip) #if QT_CONFIG(shortcut) self.actionForward.setShortcut(QCoreApplication.translate("MainWindow", u"Right", None)) #endif // QT_CONFIG(shortcut) - self.actionBack.setText(QCoreApplication.translate("MainWindow", u"actionBack", None)) + self.actionBack.setText(QCoreApplication.translate("MainWindow", u"Back", None)) #if QT_CONFIG(tooltip) self.actionBack.setToolTip(QCoreApplication.translate("MainWindow", u"One step back", None)) #endif // QT_CONFIG(tooltip) #if QT_CONFIG(shortcut) self.actionBack.setShortcut(QCoreApplication.translate("MainWindow", u"Left", None)) #endif // QT_CONFIG(shortcut) - self.actionPrint.setText(QCoreApplication.translate("MainWindow", u"Print", None)) + self.actionPrint.setText(QCoreApplication.translate("MainWindow", u"&Print", None)) #if QT_CONFIG(tooltip) self.actionPrint.setToolTip(QCoreApplication.translate("MainWindow", u"Print current file", None)) #endif // QT_CONFIG(tooltip) @@ -204,21 +197,25 @@ class Ui_MainWindow(object): #if QT_CONFIG(shortcut) self.actionAboutQt.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+I", None)) #endif // QT_CONFIG(shortcut) - self.actionRecent.setText(QCoreApplication.translate("MainWindow", u"Recently opened...", None)) + self.actionRecent.setText(QCoreApplication.translate("MainWindow", u"&Recently opened...", None)) #if QT_CONFIG(shortcut) self.actionRecent.setShortcut(QCoreApplication.translate("MainWindow", u"Meta+R", None)) #endif // QT_CONFIG(shortcut) - self.actionQuit.setText(QCoreApplication.translate("MainWindow", u"Quit", None)) + self.actionExit.setText(QCoreApplication.translate("MainWindow", u"E&xit", None)) + self.actionExit.setIconText(QCoreApplication.translate("MainWindow", u"E&xit", None)) #if QT_CONFIG(tooltip) - self.actionQuit.setToolTip(QCoreApplication.translate("MainWindow", u"Quit the application", None)) + self.actionExit.setToolTip(QCoreApplication.translate("MainWindow", u"Exits the application", None)) #endif // QT_CONFIG(tooltip) #if QT_CONFIG(shortcut) - self.actionQuit.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None)) + self.actionExit.setShortcut(QCoreApplication.translate("MainWindow", u"Ctrl+Q", None)) #endif // QT_CONFIG(shortcut) + self.actionEnglish.setText(QCoreApplication.translate("MainWindow", u"&English", None)) + self.actionDeutsch.setText(QCoreApplication.translate("MainWindow", u"&Deutsch", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.bookmarkTab), QCoreApplication.translate("MainWindow", u"Pages", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.pagesTab), QCoreApplication.translate("MainWindow", u"Bookmarks", None)) - self.qtFileMenu.setTitle(QCoreApplication.translate("MainWindow", u"File", None)) + self.qtFileMenu.setTitle(QCoreApplication.translate("MainWindow", u"&File", None)) self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) - self.mainToolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"toolBar", None)) + self.menuLanguage.setTitle(QCoreApplication.translate("MainWindow", u"Language", None)) + self.mainToolBar.setWindowTitle(QCoreApplication.translate("MainWindow", u"ToolBar", None)) # retranslateUi diff --git a/examples/demos/documentviewer/viewerfactory.py b/examples/demos/documentviewer/viewerfactory.py index 19b9f6a3..65c6a8d7 100644 --- a/examples/demos/documentviewer/viewerfactory.py +++ b/examples/demos/documentviewer/viewerfactory.py @@ -95,7 +95,7 @@ class ViewerFactory: mbox.setIcon(QMessageBox.Warning) name = mimeType.name() viewer_name = viewer.viewerName() - m = f"Mime type {name} not supported. Falling back to {viewer_name}." + m = self.tr("Mime type {} not supported. Falling back to {}.").format(name, viewer_name) mbox.setText(m) mbox.setStandardButtons(QMessageBox.Ok) QTimer.singleShot(8000, mbox.close) diff --git a/examples/demos/osmbuildings/OSMBuildings/Main.qml b/examples/demos/osmbuildings/OSMBuildings/Main.qml index aa0f65aa..ee87e560 100644 --- a/examples/demos/osmbuildings/OSMBuildings/Main.qml +++ b/examples/demos/osmbuildings/OSMBuildings/Main.qml @@ -1,37 +1,77 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +pragma ComponentBehavior: Bound import QtQuick +import QtQuick.Controls import QtQuick.Window import QtQuick3D import QtQuick3D.Helpers import OSMBuildings -Window { +ApplicationWindow { width: 1024 height: 768 visible: true title: qsTr("OSM Buildings") + id: window OSMManager { id: osmManager + onBuildingsDataReady: function( geoVariantsList, tileX, tileY, zoomLevel ){ + buildingModels.addModel(geoVariantsList, tileX, tileY, zoomLevel) + } + onMapsDataReady: function( mapData, tileX, tileY, zoomLevel ){ mapModels.addModel(mapData, tileX, tileY, zoomLevel) } } + Component { + id: chunkModelBuilding + Node { + id: node + property variant geoVariantsList: null + property int tileX: 0 + property int tileY: 0 + property int zoomLevel: 0 + Model { + id: model + scale: Qt.vector3d(1, 1, 1) + + OSMGeometry { + id: osmGeometry + Component.onCompleted: updateData( node.geoVariantsList ) + onGeometryReady:{ + model.geometry = osmGeometry + } + } + materials: [ + + CustomMaterial { + shadingMode: CustomMaterial.Shaded + cullMode: Material.BackFaceCulling + vertexShader: "customshaderbuildings.vert" + fragmentShader: "customshaderbuildings.frag" + } + ] + } + } + } + Component { id: chunkModelMap Node { + id: node property variant mapData: null property int tileX: 0 property int tileY: 0 property int zoomLevel: 0 Model { id: basePlane - position: Qt.vector3d( osmManager.tileSizeX * tileX, osmManager.tileSizeY * -tileY, 0.0 ) + position: Qt.vector3d( osmManager.tileSizeX * node.tileX, osmManager.tileSizeY * -node.tileY, 0.0 ) scale: Qt.vector3d( osmManager.tileSizeX / 100., osmManager.tileSizeY / 100., 0.5) source: "#Rectangle" materials: [ @@ -40,7 +80,7 @@ Window { enabled: true texture: Texture { textureData: CustomTextureData { - Component.onCompleted: setImageData( mapData ) + Component.onCompleted: setImageData( node.mapData ) } } } shadingMode: CustomMaterial.Shaded @@ -108,6 +148,20 @@ Window { rotation: Quaternion.fromEulerAngles(-10, -45, 0) } + Node { + id: buildingModels + + function addModel(geoVariantsList, tileX, tileY, zoomLevel) + { + chunkModelBuilding.createObject( buildingModels, { + "geoVariantsList": geoVariantsList, + "tileX": tileX, + "tileY": tileY, + "zoomLevel": zoomLevel + } ) + } + } + Node { id: mapModels @@ -230,4 +284,16 @@ Window { } } } + + Action { + id: quitAction + shortcut: StandardKey.Quit + onTriggered: window.close() + } + + onClosing: function(close) { + osmManager.stop(); + close.accepted = true; + } + } diff --git a/examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml b/examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml index 114a6dea..7c1c6eed 100644 --- a/examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml +++ b/examples/demos/osmbuildings/OSMBuildings/OSMCameraController.qml @@ -30,23 +30,23 @@ Item { implicitHeight: parent.height Connections { - target: camera + target: root.camera Component.onCompleted: { onZChanged() } function onZChanged() { // Adjust near/far values based on distance - let distance = camera.z + let distance = root.camera.z if (distance < 1) { - camera.clipNear = 0.01 - camera.clipFar = 100 + root.camera.clipNear = 0.01 + root.camera.clipFar = 100 } else if (distance < 100) { - camera.clipNear = 0.1 - camera.clipFar = 1000 + root.camera.clipNear = 0.1 + root.camera.clipFar = 1000 } else { - camera.clipNear = 1 - camera.clipFar = 10000 + root.camera.clipNear = 1 + root.camera.clipFar = 10000 } } } @@ -54,61 +54,61 @@ Item { DragHandler { id: dragHandler target: null - enabled: mouseEnabled + enabled: root.mouseEnabled acceptedModifiers: Qt.NoModifier acceptedButtons: Qt.RightButton onCentroidChanged: { - mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y), false); + root.mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y), false); } onActiveChanged: { if (active) - mousePressed(Qt.vector2d(centroid.position.x, centroid.position.y)); + root.mousePressed(Qt.vector2d(centroid.position.x, centroid.position.y)); else - mouseReleased(Qt.vector2d(centroid.position.x, centroid.position.y)); + root.mouseReleased(Qt.vector2d(centroid.position.x, centroid.position.y)); } } DragHandler { id: ctrlDragHandler target: null - enabled: mouseEnabled && panEnabled + enabled: root.mouseEnabled && root.panEnabled //acceptedModifiers: Qt.ControlModifier onCentroidChanged: { - panEvent(Qt.vector2d(centroid.position.x, centroid.position.y)); + root.panEvent(Qt.vector2d(centroid.position.x, centroid.position.y)); } onActiveChanged: { if (active) - startPan(Qt.vector2d(centroid.position.x, centroid.position.y)); + root.startPan(Qt.vector2d(centroid.position.x, centroid.position.y)); else - endPan(); + root.endPan(); } } PinchHandler { id: pinchHandler target: null - enabled: mouseEnabled + enabled: root.mouseEnabled property real distance: 0.0 onCentroidChanged: { - panEvent(Qt.vector2d(centroid.position.x, centroid.position.y)) + root.panEvent(Qt.vector2d(centroid.position.x, centroid.position.y)) } onActiveChanged: { if (active) { - startPan(Qt.vector2d(centroid.position.x, centroid.position.y)) + root.startPan(Qt.vector2d(centroid.position.x, centroid.position.y)) distance = root.camera.z } else { - endPan() + root.endPan() distance = 0.0 } } onScaleChanged: { - camera.z = distance * (1 / scale) - camera.z = Math.min(Math.max(camera.z, minimumZoom), maximumZoom) + root.camera.z = distance * (1 / scale) + root.camera.z = Math.min(Math.max(root.camera.z, root.minimumZoom), root.maximumZoom) } } @@ -120,11 +120,11 @@ Item { id: wheelHandler orientation: Qt.Vertical target: null - enabled: mouseEnabled + enabled: root.mouseEnabled onWheel: event => { let delta = -event.angleDelta.y * 0.01; - camera.z += camera.z * 0.1 * delta - camera.z = Math.min(Math.max(camera.z, minimumZoom), maximumZoom) + root.camera.z += root.camera.z * 0.1 * delta + root.camera.z = Math.min(Math.max(root.camera.z, root.minimumZoom), root.maximumZoom) } } @@ -180,26 +180,26 @@ Item { function processInput(frameDelta) { if (useMouse) { // Get the delta - var delta = Qt.vector2d(lastPos.x - currentPos.x, + let delta = Qt.vector2d(lastPos.x - currentPos.x, lastPos.y - currentPos.y); - var rotateX = delta.x * xSpeed * frameDelta - if ( xInvert ) + let rotateX = delta.x * root.xSpeed * frameDelta + if ( root.xInvert ) rotateX = -rotateX rotateAlongZ += rotateX; let rotateAlongZRad = rotateAlongZ * (Math.PI / 180.) - origin.rotate(rotateX, Qt.vector3d(0.0, 0.0, -1.0), Node.SceneSpace) + root.origin.rotate(rotateX, Qt.vector3d(0.0, 0.0, -1.0), Node.SceneSpace) - var rotateY = delta.y * -ySpeed * frameDelta - if ( yInvert ) + let rotateY = delta.y * -root.ySpeed * frameDelta + if ( root.yInvert ) rotateY = -rotateY; let preRotateAlongXY = rotateAlongXY + rotateY - if ( preRotateAlongXY <= maximumTilt && preRotateAlongXY >= minimumTilt ) + if ( preRotateAlongXY <= root.maximumTilt && preRotateAlongXY >= root.minimumTilt ) { rotateAlongXY = preRotateAlongXY - origin.rotate(rotateY, Qt.vector3d(Math.cos(rotateAlongZRad), Math.sin(-rotateAlongZRad), 0.0), Node.SceneSpace) + root.origin.rotate(rotateY, Qt.vector3d(Math.cos(rotateAlongZRad), Math.sin(-rotateAlongZRad), 0.0), Node.SceneSpace) } lastPos = currentPos; @@ -209,22 +209,22 @@ Item { let delta = currentPanPos.minus(lastPanPos); delta.x = -delta.x - delta.x = (delta.x / root.width) * camera.z * frameDelta - delta.y = (delta.y / root.height) * camera.z * frameDelta + delta.x = (delta.x / root.width) * root.camera.z * frameDelta + delta.y = (delta.y / root.height) * root.camera.z * frameDelta let velocity = Qt.vector3d(0, 0, 0) // X Movement - let xDirection = origin.right + let xDirection = root.origin.right velocity = velocity.plus(Qt.vector3d(xDirection.x * delta.x, xDirection.y * delta.x, xDirection.z * delta.x)); // Z Movement - let zDirection = origin.right.crossProduct(Qt.vector3d(0.0, 0.0, -1.0)) + let zDirection = root.origin.right.crossProduct(Qt.vector3d(0.0, 0.0, -1.0)) velocity = velocity.plus(Qt.vector3d(zDirection.x * delta.y, zDirection.y * delta.y, zDirection.z * delta.y)); - origin.position = origin.position.plus(velocity) + root.origin.position = root.origin.position.plus(velocity) lastPanPos = currentPanPos } diff --git a/examples/demos/osmbuildings/OSMBuildings/customshaderbuildings.frag b/examples/demos/osmbuildings/OSMBuildings/customshaderbuildings.frag new file mode 100644 index 00000000..da442092 --- /dev/null +++ b/examples/demos/osmbuildings/OSMBuildings/customshaderbuildings.frag @@ -0,0 +1,25 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +VARYING vec4 color; + +float rectangle(vec2 samplePosition, vec2 halfSize) { + vec2 componentWiseEdgeDistance = abs(samplePosition) - halfSize; + float outsideDistance = length(max(componentWiseEdgeDistance, 0.0)); + float insideDistance = min(max(componentWiseEdgeDistance.x, componentWiseEdgeDistance.y), 0.0); + return outsideDistance + insideDistance; +} + +void MAIN() { + vec2 tc = UV0; + vec2 uv = fract(tc * UV1.x); //UV1.x number of levels + uv = uv * 2.0 - 1.0; + uv.x = 0.0; + uv.y = smoothstep(0.0, 0.2, rectangle( vec2(uv.x, uv.y + 0.5), vec2(0.2)) ); + BASE_COLOR = vec4(color.xyz * mix( clamp( ( vec3( 0.4, 0.4, 0.4 ) + tc.y) + * ( vec3( 0.6, 0.6, 0.6 ) + uv.y) + , 0.0, 1.0), vec3(1.0), UV1.y ), 1.0); // UV1.y as is roofTop + ROUGHNESS = 0.3; + METALNESS = 0.0; + FRESNEL_POWER = 1.0; +} diff --git a/examples/demos/osmbuildings/OSMBuildings/customshaderbuildings.vert b/examples/demos/osmbuildings/OSMBuildings/customshaderbuildings.vert new file mode 100644 index 00000000..c72d8eaa --- /dev/null +++ b/examples/demos/osmbuildings/OSMBuildings/customshaderbuildings.vert @@ -0,0 +1,8 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +VARYING vec4 color; + +void MAIN() { + color = COLOR; +} diff --git a/examples/demos/osmbuildings/doc/osmbuildings.rst b/examples/demos/osmbuildings/doc/osmbuildings.rst index 487eb1d8..b4abc786 100644 --- a/examples/demos/osmbuildings/doc/osmbuildings.rst +++ b/examples/demos/osmbuildings/doc/osmbuildings.rst @@ -2,30 +2,15 @@ OSM Buildings ============= -This application shows a map obtained from OpenStreetMap (OSM) servers or a +This application shows a map obtained from `OpenStreetMap (OSM)`_ servers or a locally limited data set when the server is unavailable using :mod:`Qt Quick 3D `. -It is a subset of the equivalent C++ demo, which in addition displays -buildings. This functionality requires a special license key, though. +It is a port of the equivalent `C++ demo`_. -Queue handling --------------- +It requires the `mapbox_earcut`_ Python module to be installed:: -The application uses a queue to handle concurrent requests to boost up the -loading process of maps and building data. - -Fetching and parsing data -------------------------- - -A custom request handler class is implemented for fetching the data from the -OSM map servers. - -The downloaded ``PNG`` data is sent to a custom ``QQuick3DTextureData`` item to -convert the ``PNG`` format to a texture for map tiles. - -The application uses camera position, orientation, zoom level, and tilt to find -the nearest tiles in the view. + pip install -r requirements.txt Controls -------- @@ -42,12 +27,49 @@ When you run the application, use the following controls for navigation. | Rotate | Right mouse button + drag | n/a | +---------+---------------------------+---------+ +Fetching and parsing data +------------------------- + +A custom request handler class (class ``OSMRequest``) is implemented for +fetching the data from the OSM map servers. It uses queues to handle concurrent +requests to boost up the loading process of maps and building data . + +The application parses the online building JSON data and converts it to a list +of keys and values in geo formats such as +:class:`~PySide6.QtPositioning.QGeoPolygon` (see class ``OSMGeometry``). + +It is then sent to a custom geometry item to convert the geo coordinates +to 3D coordinates. + +The required data for the index and vertex buffers, such as position, normals, +tangents, and UV coordinates, is generated. + +The downloaded ``PNG`` map data is sent to a custom +:class:`~PySide6.QtQuick3D.QQuick3DTextureData` item to convert the ``PNG`` +format to a texture for map tiles. + +The application uses camera position, orientation, zoom level, and tilt to find +the nearest tiles in the view (see ``OSMManager.setCameraProperties()``). + Rendering --------- Every chunk of the map tile consists of a QML model (the 3D geometry) and a custom material which uses a rectangle as a base to render the tilemap texture. +The application uses a custom geometry to render tile buildings. + +The code for drawing spheres is modeled after the `OpenGL Sphere`_ example code. + +To render building parts such as rooftops with one draw call, +a custom shader is used. + + .. image:: osmbuildings.webp :align: center :alt: OSM Buildings Demo + +.. _`C++ demo`: https://doc.qt.io/qt-6/qtdoc-demos-osmbuildings-example.html +.. _`OpenStreetMap (OSM)`: https://www.openstreetmap.org/ +.. _`mapbox_earcut`: https://pypi.org/project/mapbox-earcut/ +.. _`OpenGL Sphere`: https://www.songho.ca/opengl/gl_sphere.html diff --git a/examples/demos/osmbuildings/doc/osmbuildings.webp b/examples/demos/osmbuildings/doc/osmbuildings.webp index e9cfc82d..231fa32e 100644 Binary files a/examples/demos/osmbuildings/doc/osmbuildings.webp and b/examples/demos/osmbuildings/doc/osmbuildings.webp differ diff --git a/examples/demos/osmbuildings/geometry.py b/examples/demos/osmbuildings/geometry.py new file mode 100644 index 00000000..4770cb9e --- /dev/null +++ b/examples/demos/osmbuildings/geometry.py @@ -0,0 +1,379 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import ctypes +import math +import mapbox_earcut +import numpy +from functools import partial + +from PySide6.QtQuick3D import QQuick3DGeometry +from PySide6.QtQml import QmlElement +from PySide6.QtGui import QVector3D, QColor +from PySide6.QtCore import QByteArray, QThreadPool, Qt, Signal, Slot + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "OSMBuildings" +QML_IMPORT_MAJOR_VERSION = 1 + + +FLOAT_SIZE = ctypes.sizeof(ctypes.c_float) +FLOAT_MAX = 3.40282e+38 +FLOAT_MIN = 1.17549e-38 + + +UINT32_SIZE = 4 + +STRIDE_VERTEX_LEN = 20 +# 3 Position + 3 Normal + 3 Tangent + 3 Binormal + 4 Color + 2 Texcoord0 + 2 Texcoord1 +# as Number of Levels and Is Rooftop. +STRIDE_PRIMITIVE = 3 + + +SPHERE_SECTOR_COUNT = 10 +SPHERE_STACK_COUNT = 10 + + +def convertGeoCoordToVertexPosition(lat, lon): + scale = 1.212 + geoToPositionScale = 1000000 * scale + XOffsetFromCenter = 537277 * scale + YOffsetFromCenter = 327957 * scale + x = (lon / 360.0 + 0.5) * geoToPositionScale + y = ((1.0 - math.log(math.tan(math.radians(lat)) + 1.0 / math.cos(math.radians(lat))) / math.pi) + * 0.5 * geoToPositionScale) + return QVector3D(x - XOffsetFromCenter, YOffsetFromCenter - y, 0.0) + + +def readColorProperty(properties, name, defaultColor): + if colorName := properties.get("color"): + color = QColor.fromString(colorName) + if color.isValid() and color != QColor(Qt.GlobalColor.black): + return color + return defaultColor + + +class VertexData: + """A data buffer for the vertexes consisting of STRIDE_VERTEX_LEN * float32 + entries. It can be converted to QByteArray for + QQuick3DGeometry.setVertexData().""" + def __init__(self): + self._vertexData: numpy.ndarray = None + self._index = 0 + + def vertexCount(self): + return self._vertexData.shape[0] if self._vertexData is not None else 0 + + def toByteArray(self): + return (QByteArray(self._vertexData.tobytes()) if self._vertexData is not None + else QByteArray()) + + def growBy(self, count): + if count > 0: + if self._vertexData is None: + self._vertexData = numpy.ndarray(shape=(count, STRIDE_VERTEX_LEN), + dtype=numpy.float32) + else: + oldSize = self.vertexCount() + self._vertexData.resize((oldSize + count, STRIDE_VERTEX_LEN), refcheck=False) + + def append(self, pos, normal, tangent, binormal, color, alpha, + texCoordX, texCoordY, levels, isRoofTop): + self.set(self._index, pos, normal, tangent, binormal, color, alpha, + texCoordX, texCoordY, levels, isRoofTop) + self._index += 1 + + def set(self, index, pos, normal, tangent, binormal, color, alpha, + texCoordX, texCoordY, levels, isRoofTop): + self._vertexData[index][0] = pos.x() + self._vertexData[index][1] = pos.y() + self._vertexData[index][2] = pos.z() + + self._vertexData[index][3] = normal.x() + self._vertexData[index][4] = normal.y() + self._vertexData[index][5] = normal.z() + + self._vertexData[index][6] = tangent.x() + self._vertexData[index][7] = tangent.y() + self._vertexData[index][8] = tangent.z() + + self._vertexData[index][9] = binormal.x() + self._vertexData[index][10] = binormal.y() + self._vertexData[index][11] = binormal.z() + + self._vertexData[index][12] = color.redF() + self._vertexData[index][13] = color.greenF() + self._vertexData[index][14] = color.blueF() + self._vertexData[index][15] = alpha + + self._vertexData[index][16] = texCoordX + self._vertexData[index][17] = texCoordY + + self._vertexData[index][18] = levels + self._vertexData[index][19] = isRoofTop + + +class IndexData: + """A data buffer for the vertex indexes consisting uint32 entries. It can be + converted to QByteArray for QQuick3DGeometry.setIndexData().""" + def __init__(self): + self._indexData: numpy.ndarray = None + self._index = 0 + + def indexCount(self): + return self._indexData.shape[0] if self._indexData is not None else 0 + + def toByteArray(self): + return (QByteArray(self._indexData.tobytes()) if self._indexData is not None + else QByteArray()) + + def growBy(self, count): + if count > 0: + if self._indexData is None: + self._indexData = numpy.ndarray(shape=(count), + dtype=numpy.uint32) + else: + oldSize = self.indexCount() + self._indexData.resize((oldSize + count), refcheck=False) + + def append(self, v): + self._indexData[self._index] = v + self._index += 1 + + def append3(self, v1, v2, v3): + self._indexData[self._index] = v1 + self._index += 1 + self._indexData[self._index] = v2 + self._index += 1 + self._indexData[self._index] = v3 + self._index += 1 + + +@QmlElement +class OSMGeometry(QQuick3DGeometry): + geometryReady = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + + @Slot("QVariantList") + def updateData(self, geoVariantsList): + QThreadPool.globalInstance().start(partial(self.loadGeometryFromData, geoVariantsList)) + + def loadGeometryFromData(self, geoVariantsList): + + meshMinBound = QVector3D(FLOAT_MAX, FLOAT_MAX, FLOAT_MAX) + meshMaxBound = QVector3D(FLOAT_MIN, FLOAT_MIN, FLOAT_MIN) + + globalVertexCounter = 0 + globalPrimitiveCounter = 0 + + vertexData = VertexData() + indexData = IndexData() + + for baseData in geoVariantsList: + for featureMap in baseData["data"]: + properties = featureMap["properties"] + buildingCoords = featureMap["data"].perimeter() + height = 0.15 * properties["height"] + levels = float(properties.get("levels", 0)) + color = readColorProperty(properties, "color", QColor(Qt.GlobalColor.white)) + roofColor = readColorProperty(properties, "roofColor", color) + subsetMinBound = QVector3D(FLOAT_MAX, FLOAT_MAX, FLOAT_MAX) + subsetMaxBound = QVector3D(FLOAT_MIN, FLOAT_MIN, FLOAT_MIN) + + numSubsetVertices = len(buildingCoords) * 2 + + vertexData.growBy(numSubsetVertices) + indexData.growBy((numSubsetVertices - 2) * STRIDE_PRIMITIVE) + + subsetVertexCounter = 0 + + lastBaseVertexPos = QVector3D() + lastExtrudedVertexPos = QVector3D() + currentBaseVertexPos = QVector3D() + currentExtrudedVertexPos = QVector3D() + subsetPolygonCenter = QVector3D() + + roofPolygonVertices = numpy.ndarray(shape=(len(buildingCoords), 2), + dtype=numpy.float32) + for b, buildingPoint in enumerate(buildingCoords): + lastBaseVertexPos = currentBaseVertexPos + lastExtrudedVertexPos = currentExtrudedVertexPos + + currentBaseVertexPos = convertGeoCoordToVertexPosition(buildingPoint.latitude(), # noqa: E501 + buildingPoint.longitude()) # noqa: E501 + currentExtrudedVertexPos = QVector3D(currentBaseVertexPos.x(), + currentBaseVertexPos.y(), + height) + + roofPolygonVertices[b][0] = currentBaseVertexPos.x() + roofPolygonVertices[b][1] = currentBaseVertexPos.y() + + subsetPolygonCenter.setX(subsetPolygonCenter.x() + currentBaseVertexPos.x()) + subsetPolygonCenter.setY(subsetPolygonCenter.y() + currentBaseVertexPos.y()) + + meshMinBound.setX(min(meshMinBound.x(), currentBaseVertexPos.x())) + meshMinBound.setY(min(meshMinBound.y(), currentBaseVertexPos.y())) + meshMinBound.setZ(min(meshMinBound.z(), currentBaseVertexPos.z())) + + meshMaxBound.setX(max(meshMaxBound.x(), currentExtrudedVertexPos.x())) + meshMaxBound.setY(max(meshMaxBound.y(), currentExtrudedVertexPos.y())) + meshMaxBound.setZ(max(meshMaxBound.z(), currentExtrudedVertexPos.z())) + + subsetMinBound.setX(min(subsetMinBound.x(), currentBaseVertexPos.x())) + subsetMinBound.setY(min(subsetMinBound.y(), currentBaseVertexPos.y())) + subsetMinBound.setZ(min(subsetMinBound.z(), currentBaseVertexPos.z())) + + subsetMaxBound.setX(max(subsetMaxBound.x(), currentExtrudedVertexPos.x())) + subsetMaxBound.setY(max(subsetMaxBound.y(), currentExtrudedVertexPos.y())) + subsetMaxBound.setZ(max(subsetMaxBound.z(), currentExtrudedVertexPos.z())) + + if subsetVertexCounter < numSubsetVertices - 2: + indexData.append3(globalVertexCounter + 3, globalVertexCounter + 2, + globalVertexCounter + 0) + indexData.append3(globalVertexCounter + 1, globalVertexCounter + 3, + globalVertexCounter + 0) + + globalPrimitiveCounter += 2 + + if subsetVertexCounter == 2: + tangent = (currentExtrudedVertexPos - currentBaseVertexPos).normalized() + binormal = (lastBaseVertexPos - currentBaseVertexPos).normalized() + normal = QVector3D.crossProduct(binormal, tangent).normalized() + + vertexData.append(lastBaseVertexPos, normal, tangent, + binormal, color, 1, 0, 0, levels, 0.0) + + vertexData.append(lastExtrudedVertexPos, normal, + tangent, binormal, color, 1, 0, 0, levels, 0.0) + + if subsetVertexCounter >= 2: + tangent = (currentExtrudedVertexPos - currentBaseVertexPos).normalized() + binormal = (lastBaseVertexPos - currentBaseVertexPos).normalized() + normal = QVector3D.crossProduct(binormal, tangent).normalized() + + xCoord = 1.0 if subsetVertexCounter % 4 != 0 else 0.0 + vertexData.append(currentBaseVertexPos, normal, tangent, + binormal, color, 1, xCoord, 0, levels, 0.0) + + vertexData.append(currentExtrudedVertexPos, normal, + tangent, binormal, color, 1, xCoord, 0, levels, 0.0) + + subsetVertexCounter += 2 + globalVertexCounter += 2 + + if properties.get("shape", "") == "sphere": + subsetPolygonCenter = QVector3D(subsetPolygonCenter.x() + / len(roofPolygonVertices), + subsetPolygonCenter.y() + / len(roofPolygonVertices), + height) + + sphereRadius = 2.0 * abs(roofPolygonVertices[0][0] - subsetPolygonCenter.x()) + + sphereRadius = max(sphereRadius, 1.0) + sphereRadiuslengthInv = 1.0 / sphereRadius + + sphereSectorStep = 2.0 * math.pi / SPHERE_SECTOR_COUNT + sphereStackStep = math.pi / SPHERE_STACK_COUNT + + sphereVertexCount = (SPHERE_STACK_COUNT + 1) * (SPHERE_SECTOR_COUNT + 1) + vertexData.growBy(sphereVertexCount) + indexData.growBy(sphereVertexCount * 2 * STRIDE_PRIMITIVE) + + for stackIndex in range(0, SPHERE_STACK_COUNT + 1): + k1 = stackIndex * (SPHERE_SECTOR_COUNT + 1) + k2 = k1 + SPHERE_SECTOR_COUNT + 1 + + sphereStackAngle = math.pi / 2.0 - stackIndex * sphereStackStep + xy = sphereRadius * math.cos(sphereStackAngle) + z = sphereRadius * math.sin(sphereStackAngle) + + for sectorIndex in range(0, SPHERE_SECTOR_COUNT + 1): + if stackIndex != SPHERE_STACK_COUNT: + if stackIndex != 0: + indexData.append3(k1 + globalVertexCounter, + k2 + globalVertexCounter, + k1 + 1 + globalVertexCounter) + globalPrimitiveCounter += 1 + + if stackIndex != (SPHERE_STACK_COUNT - 1): + indexData.append3(k1 + 1 + globalVertexCounter, + k2 + globalVertexCounter, + k2 + 1 + globalVertexCounter) + globalPrimitiveCounter += 1 + + sphereSectorAngle = sectorIndex * sphereSectorStep + + x = xy * math.cos(sphereSectorAngle) + y = xy * math.sin(sphereSectorAngle) + + position = QVector3D(x + subsetPolygonCenter.x(), + y + subsetPolygonCenter.y(), + z + subsetPolygonCenter.z()) + normal = QVector3D(x * sphereRadiuslengthInv, + y * sphereRadiuslengthInv, + z * sphereRadiuslengthInv) + + vertexData.append(position, normal, + QVector3D(0, 0, 0), QVector3D(0, 0, 0), + roofColor, 1, 1.0, 1.0, 0.0, 1.0) + + k1 += 1 + k2 += 1 + + subsetVertexCounter += sphereVertexCount + globalVertexCounter += sphereVertexCount + + rings = numpy.array([len(roofPolygonVertices)], dtype=numpy.uint32) + roofIndices = mapbox_earcut.triangulate_float32(roofPolygonVertices, rings) + + vertexData.growBy(len(roofPolygonVertices)) + indexData.growBy(len(roofIndices)) + + for roofIndex in roofIndices: + indexData.append(roofIndex + globalVertexCounter) + + roofPrimitiveCount = int(len(roofIndices) / 3) + globalPrimitiveCounter += roofPrimitiveCount + + for polygonVertex in roofPolygonVertices: + position = QVector3D(polygonVertex[0], polygonVertex[1], height) + normal = QVector3D(0.0, 0.0, 1.0) + tangent = QVector3D(1.0, 0.0, 0.0) + binormal = QVector3D(0.0, 1.0, 0.0) + vertexData.append(position, normal, tangent, + binormal, roofColor, 1.0, 1.0, 1.0, 0.0, 1.0) + + subsetVertexCounter += 1 + globalVertexCounter += 1 + + self.clear() + + self.setIndexData(indexData.toByteArray()) + self.setVertexData(vertexData.toByteArray()) + self.setStride(STRIDE_VERTEX_LEN * FLOAT_SIZE) + + self.setBounds(meshMinBound, meshMaxBound) + + self.setPrimitiveType(QQuick3DGeometry.PrimitiveType.Triangles) + self.addAttribute(QQuick3DGeometry.Attribute.IndexSemantic, 0, + QQuick3DGeometry.Attribute.U32Type) + self.addAttribute(QQuick3DGeometry.Attribute.PositionSemantic, 0, + QQuick3DGeometry.Attribute.F32Type) + self.addAttribute(QQuick3DGeometry.Attribute.NormalSemantic, 3 * FLOAT_SIZE, + QQuick3DGeometry.Attribute.F32Type) + self.addAttribute(QQuick3DGeometry.Attribute.TangentSemantic, 6 * FLOAT_SIZE, + QQuick3DGeometry.Attribute.F32Type) + self.addAttribute(QQuick3DGeometry.Attribute.BinormalSemantic, 9 * FLOAT_SIZE, + QQuick3DGeometry.Attribute.F32Type) + self.addAttribute(QQuick3DGeometry.Attribute.ColorSemantic, 12 * FLOAT_SIZE, + QQuick3DGeometry.Attribute.F32Type) + self.addAttribute(QQuick3DGeometry.Attribute.TexCoord0Semantic, 16 * FLOAT_SIZE, + QQuick3DGeometry.Attribute.F32Type) + self.addAttribute(QQuick3DGeometry.Attribute.TexCoord1Semantic, 18 * FLOAT_SIZE, + QQuick3DGeometry.Attribute.F32Type) + self.update() + self.geometryReady.emit() diff --git a/examples/demos/osmbuildings/main.py b/examples/demos/osmbuildings/main.py index 313e3a0f..7221a1ee 100644 --- a/examples/demos/osmbuildings/main.py +++ b/examples/demos/osmbuildings/main.py @@ -1,17 +1,25 @@ # Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +import argparse import sys from pathlib import Path from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtGui import QGuiApplication -from PySide6.QtCore import QCoreApplication +from geometry import OSMGeometry # noqa: F401 from manager import OSMManager, CustomTextureData # noqa: F401 if __name__ == "__main__": + parser = argparse.ArgumentParser(description="OSM Buildings") + parser.add_argument("--disable-buildings", "-b", action="store_true") + args = parser.parse_args() + + if args.disable_buildings: + OSMManager.buildings = False + app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() engine.addImportPath(Path(__file__).parent) @@ -19,6 +27,6 @@ if __name__ == "__main__": if not engine.rootObjects(): sys.exit(-1) - exit_code = QCoreApplication.exec() + exit_code = QGuiApplication.exec() del engine sys.exit(exit_code) diff --git a/examples/demos/osmbuildings/manager.py b/examples/demos/osmbuildings/manager.py index 6ff2d204..0490297f 100644 --- a/examples/demos/osmbuildings/manager.py +++ b/examples/demos/osmbuildings/manager.py @@ -4,7 +4,7 @@ from PySide6.QtQuick3D import QQuick3DTextureData from PySide6.QtQml import QmlElement from PySide6.QtGui import QImage, QVector3D -from PySide6.QtCore import QByteArray, QObject, Property, Slot, Signal +from PySide6.QtCore import QByteArray, QObject, QThreadPool, Property, Slot, Signal from request import OSMTileData, OSMRequest @@ -17,7 +17,9 @@ QML_IMPORT_MAJOR_VERSION = 1 @QmlElement class OSMManager(QObject): + buildingsDataReady = Signal(list, int, int, int) mapsDataReady = Signal(QByteArray, int, int, int) + buildings = True def __init__(self, parent=None): super().__init__(parent) @@ -26,7 +28,17 @@ class OSMManager(QObject): self.m_startBuildingTileY = 10746 self.m_tileSizeX = 37 self.m_tileSizeY = 37 + self.m_request.buildingsDataReady.connect(self._slotBuildingsDataReady) self.m_request.mapsDataReady.connect(self._slotMapsDataReady) + self.m_buildingsHash = set() + + @Slot() + def stop(self): + self.m_request.stop() + self.m_request.buildingsDataReady.disconnect(self._slotBuildingsDataReady) + self.m_request.mapsDataReady.disconnect(self._slotMapsDataReady) + # Stop the threads started by OSMGeometry in the global pool + QThreadPool.globalInstance().waitForDone() def tileSizeX(self): return self.m_tileSizeX @@ -34,6 +46,13 @@ class OSMManager(QObject): def tileSizeY(self): return self.m_tileSizeY + @Slot(list, int, int, int) + def _slotBuildingsDataReady(self, geoVariantsList, tileX, tileY, zoomLevel): + self.m_buildingsHash.add(OSMTileData(tileX, tileY, zoomLevel)) + self.buildingsDataReady.emit(geoVariantsList, tileX - self.m_startBuildingTileX, + tileY - self.m_startBuildingTileY, + zoomLevel) + @Slot(QByteArray, int, int, int) def _slotMapsDataReady(self, mapData, tileX, tileY, zoomLevel): self.mapsDataReady.emit(mapData, tileX - self.m_startBuildingTileX, @@ -71,10 +90,14 @@ class OSMManager(QObject): queue.sort(key=tile_sort_key) + if self.buildings: + self.m_request.getBuildingsData(queue.copy()) self.m_request.getMapsData(queue.copy()) def addBuildingRequestToQueue(self, queue, tileX, tileY, zoomLevel=15): - queue.append(OSMTileData(tileX, tileY, zoomLevel)) + data = OSMTileData(tileX, tileY, zoomLevel) + if data not in self.m_buildingsHash: + queue.append(data) @Slot(result=bool) def isDemoToken(self): diff --git a/examples/demos/osmbuildings/osmbuildings.pyproject b/examples/demos/osmbuildings/osmbuildings.pyproject index 2d023522..f743836b 100644 --- a/examples/demos/osmbuildings/osmbuildings.pyproject +++ b/examples/demos/osmbuildings/osmbuildings.pyproject @@ -1,5 +1,8 @@ { - "files": ["main.py", "manager.py", "request.py", + "files": ["main.py", "manager.py", "request.py", "geometry.py", "OSMBuildings/Main.qml", "OSMBuildings/OSMCameraController.qml", + "OSMBuildings/qmldir", + "OSMBuildings/customshaderbuildings.frag", + "OSMBuildings/customshaderbuildings.vert", "OSMBuildings/customshadertiles.frag"] } diff --git a/examples/demos/osmbuildings/request.py b/examples/demos/osmbuildings/request.py index a845bc29..9fa1d3d0 100644 --- a/examples/demos/osmbuildings/request.py +++ b/examples/demos/osmbuildings/request.py @@ -4,16 +4,29 @@ import math import sys from dataclasses import dataclass +from enum import IntEnum from functools import partial +from PySide6.QtPositioning import QGeoCoordinate, QGeoPolygon from PySide6.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest -from PySide6.QtCore import (QByteArray, QTimer, QFile, QFileInfo, +from PySide6.QtCore import (QByteArray, QTimer, QFile, QFileInfo, QJsonDocument, QObject, QUrl, Signal, Slot) +# %1 = zoom level(15 the default and only one here that seems working), +# %2 = x tile number, %3 = y tile number +URL_OSMB_JSON = ("https://983wdxn2c2.execute-api.eu-north-1.amazonaws.com/production/" + "osmbuildingstile?z={}&x={}&y={}&token={}") + # %1 = zoom level(is dynamic), %2 = x tile number, %3 = y tile number URL_OSMB_MAP = "https://tile-a.openstreetmap.fr/hot/{}/{}/{}.png" +class GeoTypeSwitch(IntEnum): + Polygon = 0 + Feature = 1 + FeatureCollection = 2 + + @dataclass class OSMTileData: TileX: int = 0 @@ -43,13 +56,125 @@ def tileKey(tile): return f"{tile.ZoomLevel},{tile.TileX},{tile.TileY}" +def importPosition(position): + returnedCoordinates = QGeoCoordinate() + if position: + returnedCoordinates.setLongitude(position[0]) + if len(position) > 1: + returnedCoordinates.setLatitude(position[1]) + if len(position) > 2: + returnedCoordinates.setAltitude(position[2]) + return returnedCoordinates + + +def importArrayOfPositions(arrayOfPositions): + returnedCoordinates = [] + for position in arrayOfPositions: + coordinate = importPosition(position) + if coordinate.isValid(): + returnedCoordinates.append(coordinate) # Populating the QList of coordinates + return returnedCoordinates + + +def importArrayOfArrayOfPositions(arrayOfArrayofPositions): + returnedCoordinates = [] + for position in arrayOfArrayofPositions: + returnedCoordinates.append(importArrayOfPositions(position)) + return returnedCoordinates + + +def importPolygon(inputMap): + returnedObject = QGeoPolygon() + valueCoordinates = inputMap.get("coordinates") + for i, p in enumerate(importArrayOfArrayOfPositions(valueCoordinates)): + if i == 0: + returnedObject.setPerimeter(p) # External perimeter + else: + returnedObject.addHole(p) # Inner perimeters + return returnedObject + + +def importGeometry(inputMap): + returnedObject = {} + geometryTypes = ["Polygon"] + for i in range(len(geometryTypes)): + if inputMap.get("type") == geometryTypes[i]: + if i == 0: + returnedObject["type"] = "Polygon" + returnedObject["data"] = importPolygon(inputMap) + return returnedObject + + +def importFeatureCollection(inputMap): + returnedObject = [] + featuresList = inputMap.get("features") + for inputfeature in featuresList: + inputFeatureMap = inputfeature + singleFeatureMap = importGeometry(inputFeatureMap.get("geometry")) + importedProperties = inputFeatureMap.get("properties") + singleFeatureMap["properties"] = importedProperties + if "id" in inputFeatureMap: + importedId = inputFeatureMap.get("id") + singleFeatureMap["id"] = importedId + + returnedObject.append(singleFeatureMap) + return returnedObject + + +def importGeoJson(geoJson): + returnedList = [] + rootGeoJsonObject = geoJson.object() # Read json object from imported doc + + geoType = ["Polygon", "Feature", "FeatureCollection"] + geometryTypesLen = len(geoType) + + parsedGeoJsonMap = {} + + # Checking whether the JSON object has a "type" member + valueType = rootGeoJsonObject.get("type") + + # Checking whether the "type" member has a GeoJSON admitted value + for i in range(geometryTypesLen): + if valueType == geoType[i]: + if i == GeoTypeSwitch.Polygon: + poly = importPolygon(rootGeoJsonObject) + parsedGeoJsonMap.insert("type", "Polygon") + parsedGeoJsonMap.insert("data", poly) + + # Single GeoJson geometry object with properties + elif i == GeoTypeSwitch.Feature: + parsedGeoJsonMap = importGeometry(rootGeoJsonObject.get("geometry")) + importedProperties = rootGeoJsonObject.get("properties") + parsedGeoJsonMap.insert("properties", importedProperties) + id_value = rootGeoJsonObject.get("id") + if id_value: + parsedGeoJsonMap.insert("id", id_value) + # Heterogeneous list of GeoJSON geometries with properties + elif i == GeoTypeSwitch.FeatureCollection: + featCollection = importFeatureCollection(rootGeoJsonObject) + parsedGeoJsonMap["type"] = "FeatureCollection" + parsedGeoJsonMap["data"] = featCollection + + bboxNodeValue = rootGeoJsonObject.get("bbox") + if bboxNodeValue is not None: + parsedGeoJsonMap["bbox"] = bboxNodeValue + returnedList.append(parsedGeoJsonMap) + + elif i >= 9: + # Error + break + return returnedList + + class OSMRequest(QObject): + buildingsDataReady = Signal(list, int, int, int) mapsDataReady = Signal(QByteArray, int, int, int) def __init__(self, parent): super().__init__(parent) + self.m_buildingsNumberOfRequestsInFlight = 0 self.m_mapsNumberOfRequestsInFlight = 0 self.m_queuesTimer = QTimer() self.m_queuesTimer.setInterval(0) @@ -63,12 +188,22 @@ class OSMRequest(QObject): self.m_lastBuildingsDataError = "" self.m_lastMapsDataError = "" + @Slot() + def stop(self): + if self.m_queuesTimer.isActive(): + self.m_queuesTimer.stop() + @Slot() def _slotTimeOut(self): if not self.m_buildingsQueue and not self.m_mapsQueue: self.m_queuesTimer.stop() else: numConcurrentRequests = 6 + if (self.m_buildingsQueue + and self.m_buildingsNumberOfRequestsInFlight < numConcurrentRequests): + self.getBuildingsDataRequest(self.m_buildingsQueue[0]) + del self.m_buildingsQueue[0] + self.m_buildingsNumberOfRequestsInFlight += 1 if self.m_mapsQueue and self.m_mapsNumberOfRequestsInFlight < numConcurrentRequests: self.getMapsDataRequest(self.m_mapsQueue[0]) del self.m_mapsQueue[0] @@ -91,6 +226,38 @@ class OSMRequest(QObject): if not self.m_queuesTimer.isActive(): self.m_queuesTimer.start() + def getBuildingsDataRequest(self, tile): + fileName = "data/" + tileKey(tile) + ".json" + if QFileInfo.exists(fileName): + file = QFile(fileName) + if file.open(QFile.ReadOnly): + data = file.readAll() + file.close() + doc = QJsonDocument.fromJson(data) + self.buildingsDataReady.emit(importGeoJson(doc), + tile.TileX, tile.TileY, tile.ZoomLevel) + self.m_buildingsNumberOfRequestsInFlight -= 1 + return + + url = QUrl(URL_OSMB_JSON.format(tile.ZoomLevel, tile.TileX, tile.TileY, self.m_token)) + reply = self.m_networkAccessManager.get(QNetworkRequest(url)) + reply.finished.connect(partial(self._buildingsDataReceived, reply, tile)) + + @Slot(OSMTileData) + def _buildingsDataReceived(self, reply, tile): + reply.deleteLater() + if reply.error() == QNetworkReply.NoError: + data = reply.readAll() + self.buildingsDataReady.emit(importGeoJson(QJsonDocument.fromJson(data)), + tile.TileX, tile.TileY, tile.ZoomLevel) + else: + message = reply.readAll().data().decode('utf-8') + if message != self.m_lastBuildingsDataError: + self.m_lastBuildingsDataError = message + print("OSMRequest.getBuildingsData ", reply.error(), + reply.url(), message, file=sys.stderr) + self.m_buildingsNumberOfRequestsInFlight -= 1 + def getMapsData(self, mapsQueue): if not mapsQueue: return diff --git a/examples/demos/osmbuildings/requirements.txt b/examples/demos/osmbuildings/requirements.txt new file mode 100644 index 00000000..2e1daae3 --- /dev/null +++ b/examples/demos/osmbuildings/requirements.txt @@ -0,0 +1 @@ +mapbox_earcut diff --git a/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py b/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py index 984bf9df..057ea69f 100644 --- a/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py +++ b/examples/graphs/3d/widgetgraphgallery/scatterdatamodifier.py @@ -116,14 +116,15 @@ class ScatterDataModifier(QObject): @Slot(QtGraphs3D.ElementType) def handleElementSelected(self, type): - if type == QtGraphs3D.ElementType.AxisXLabel: - self._state = InputState.StateDraggingX - elif type == QtGraphs3D.ElementType.AxisYLabel: - self._state = InputState.StateDraggingY - elif type == QtGraphs3D.ElementType.AxisZLabel: - self._state = InputState.StateDraggingZ - else: - self._state = InputState.StateNormal + match type: + case QtGraphs3D.ElementType.AxisXLabel: + self._state = InputState.StateDraggingX + case QtGraphs3D.ElementType.AxisYLabel: + self._state = InputState.StateDraggingY + case QtGraphs3D.ElementType.AxisZLabel: + self._state = InputState.StateDraggingZ + case _: + self._state = InputState.StateNormal @Slot(QVector2D) def handleAxisDragging(self, delta): @@ -145,19 +146,20 @@ class ScatterDataModifier(QObject): yMove = -move.y() if yRotation < 0 else move.y() # Adjust axes - if self._state == InputState.StateDraggingX: - axis = self._graph.axisX() - distance = (move.x() * xMulX - yMove * xMulY) / self._dragSpeedModifier - axis.setRange(axis.min() - distance, axis.max() - distance) - elif self._state == InputState.StateDraggingZ: - axis = self._graph.axisZ() - distance = (move.x() * zMulX + yMove * zMulY) / self._dragSpeedModifier - axis.setRange(axis.min() + distance, axis.max() + distance) - elif self._state == InputState.StateDraggingY: - axis = self._graph.axisY() - # No need to use adjusted y move here - distance = move.y() / self._dragSpeedModifier - axis.setRange(axis.min() + distance, axis.max() + distance) + match self._state: + case InputState.StateDraggingX: + axis = self._graph.axisX() + distance = (move.x() * xMulX - yMove * xMulY) / self._dragSpeedModifier + axis.setRange(axis.min() - distance, axis.max() - distance) + case InputState.StateDraggingZ: + axis = self._graph.axisZ() + distance = (move.x() * zMulX + yMove * zMulY) / self._dragSpeedModifier + axis.setRange(axis.min() + distance, axis.max() + distance) + case InputState.StateDraggingY: + axis = self._graph.axisY() + # No need to use adjusted y move here + distance = move.y() / self._dragSpeedModifier + axis.setRange(axis.min() + distance, axis.max() + distance) @Slot(int) def changeShadowQuality(self, quality): diff --git a/examples/graphs/common/graphprinting/GraphPrintingExample/qmldir b/examples/graphs/common/graphprinting/GraphPrintingExample/qmldir index 0cd6a5ba..ad59ee00 100644 --- a/examples/graphs/common/graphprinting/GraphPrintingExample/qmldir +++ b/examples/graphs/common/graphprinting/GraphPrintingExample/qmldir @@ -2,3 +2,4 @@ module GraphPrintingExample Main 1.0 Main.qml Graph2D 1.0 Graph2D.qml Graph3D 1.0 Graph3D.qml +typeinfo graphprinter.qmltypes diff --git a/examples/gui/rhiwindow/rhiwindow.py b/examples/gui/rhiwindow/rhiwindow.py index e9b6c1d1..81f3a5b9 100644 --- a/examples/gui/rhiwindow/rhiwindow.py +++ b/examples/gui/rhiwindow/rhiwindow.py @@ -63,17 +63,15 @@ class RhiWindow(QWindow): self.m_viewProjection = QMatrix4x4() self.m_graphicsApi = graphicsApi - - if graphicsApi == QRhi.Implementation.OpenGLES2: - self.setSurfaceType(QSurface.SurfaceType.OpenGLSurface) - elif graphicsApi == QRhi.Implementation.Vulkan: - self.setSurfaceType(QSurface.SurfaceType.VulkanSurface) - elif graphicsApi == QRhi.Implementation.D3D11 or graphicsApi == QRhi.Implementation.D3D12: - self.setSurfaceType(QSurface.SurfaceType.Direct3DSurface) - elif graphicsApi == QRhi.Implementation.Metal: - self.setSurfaceType(QSurface.SurfaceType.MetalSurface) - elif graphicsApi == QRhi.Implementation.Null: - pass # RasterSurface + match graphicsApi: + case QRhi.Implementation.OpenGLES2: + self.setSurfaceType(QSurface.SurfaceType.OpenGLSurface) + case QRhi.Implementation.Vulkan: + self.setSurfaceType(QSurface.SurfaceType.VulkanSurface) + case QRhi.Implementation.D3D11 | QRhi.Implementation.D3D12: + self.setSurfaceType(QSurface.SurfaceType.Direct3DSurface) + case QRhi.Implementation.Metal: + self.setSurfaceType(QSurface.SurfaceType.MetalSurface) def __del__(self): # destruction order matters to a certain degree: the fallbackSurface @@ -152,31 +150,32 @@ class RhiWindow(QWindow): return super().event(e) def init(self): - if self.m_graphicsApi == QRhi.Implementation.Null: - params = QRhiNullInitParams() - self.m_rhi = QRhi.create(QRhi.Implementation.Null, params) - - if self.m_graphicsApi == QRhi.Implementation.OpenGLES2: - self.m_fallbackSurface = QRhiGles2InitParams.newFallbackSurface() - params = QRhiGles2InitParams() - params.fallbackSurface = self.m_fallbackSurface - params.window = self - self.m_rhi = QRhi.create(QRhi.Implementation.OpenGLES2, params) - elif self.m_graphicsApi == QRhi.Implementation.D3D11: - params = QRhiD3D11InitParams() - # Enable the debug layer, if available. This is optional - # and should be avoided in production builds. - params.enableDebugLayer = True - self.m_rhi = QRhi.create(QRhi.Implementation.D3D11, params) - elif self.m_graphicsApi == QRhi.Implementation.D3D12: - params = QRhiD3D12InitParams() - # Enable the debug layer, if available. This is optional - # and should be avoided in production builds. - params.enableDebugLayer = True - self.m_rhi = QRhi.create(QRhi.Implementation.D3D12, params) - elif self.m_graphicsApi == QRhi.Implementation.Metal: - params = QRhiMetalInitParams() - self.m_rhi = QRhi.create(QRhi.Implementation.Metal, params) + match self.m_graphicsApi: + case QRhi.Implementation.Null: + params = QRhiNullInitParams() + self.m_rhi = QRhi.create(QRhi.Implementation.Null, params) + + case QRhi.Implementation.OpenGLES2: + self.m_fallbackSurface = QRhiGles2InitParams.newFallbackSurface() + params = QRhiGles2InitParams() + params.fallbackSurface = self.m_fallbackSurface + params.window = self + self.m_rhi = QRhi.create(QRhi.Implementation.OpenGLES2, params) + case QRhi.Implementation.D3D11: + params = QRhiD3D11InitParams() + # Enable the debug layer, if available. This is optional + # and should be avoided in production builds. + params.enableDebugLayer = True + self.m_rhi = QRhi.create(QRhi.Implementation.D3D11, params) + case QRhi.Implementation.D3D12: + params = QRhiD3D12InitParams() + # Enable the debug layer, if available. This is optional + # and should be avoided in production builds. + params.enableDebugLayer = True + self.m_rhi = QRhi.create(QRhi.Implementation.D3D12, params) + case QRhi.Implementation.Metal: + params = QRhiMetalInitParams() + self.m_rhi = QRhi.create(QRhi.Implementation.Metal, params) if not self.m_rhi: qFatal("Failed to create RHI backend") diff --git a/examples/installer_test/hello.py b/examples/installer_test/hello.py index 162327c4..31156f34 100644 --- a/examples/installer_test/hello.py +++ b/examples/installer_test/hello.py @@ -54,10 +54,7 @@ class MyWidget(QWidget): if __name__ == "__main__": print("Start of hello.py ", time.ctime()) print(" sys.version = ", sys.version.splitlines()[0]) - # Nuitka and hence pyside6-deploy fails on Python versions <= 3.9 - # when this module is used - if sys.version_info.minor > 9: - print(" platform.platform() = ", platform.platform()) + print(" platform.platform() = ", platform.platform()) app = QApplication() diff --git a/examples/installer_test/hello_app.spec b/examples/installer_test/hello_app.spec index 5ae00a01..e354726b 100644 --- a/examples/installer_test/hello_app.spec +++ b/examples/installer_test/hello_app.spec @@ -20,14 +20,6 @@ a = Analysis(['hello.py'], hiddenimports=[], hookspath=[], runtime_hooks=[], - # 2019-04-28 - # This hack circumvents a side effect of Python 2.7.16 which leads to a failure - # in 'hook-_tkinter.py'. The error is reported. Until it is fixed, we circumvent - # the problem by this exclude. - # This effect is triggered by installing 'numpy'. It is somewhat special since - # the problem does not show up in Python 3.7 . tkinter would have the same - # problem on Python 3.7, but numpy would not trigger it for some reason. - excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, diff --git a/examples/location/mapviewer/MapViewer/qmldir b/examples/location/mapviewer/MapViewer/qmldir index 359ca02a..0e11a510 100644 --- a/examples/location/mapviewer/MapViewer/qmldir +++ b/examples/location/mapviewer/MapViewer/qmldir @@ -1,5 +1,4 @@ module MapViewer -typeinfo mapviewer.qmltypes Main 1.0 Main.qml Helper 1.0 helper.js MapComponent 1.0 map/MapComponent.qml diff --git a/examples/multimedia/audiooutput/audiooutput.py b/examples/multimedia/audiooutput/audiooutput.py index 9f1088cd..946a326d 100644 --- a/examples/multimedia/audiooutput/audiooutput.py +++ b/examples/multimedia/audiooutput/audiooutput.py @@ -37,22 +37,24 @@ class Generator(QIODevice): def generate_data(self, fmt, durationUs, sampleRate): pack_format = '' - sample_size = fmt.bytesPerSample() * 8 - if sample_size == 8: - if fmt.sampleFormat() == QAudioFormat.SampleFormat.UInt8: - scaler = lambda x: ((1.0 + x) / 2 * 255) # noqa: E731 - pack_format = 'B' - elif fmt.sampleFormat() == QAudioFormat.SampleFormat.Int16: - scaler = lambda x: x * 127 # noqa: E731 - pack_format = 'b' - elif sample_size == 16: - little_endian = QSysInfo.Endian.ByteOrder == QSysInfo.Endian.LittleEndian - if fmt.sampleFormat() == QAudioFormat.SampleFormat.UInt8: - scaler = lambda x: (1.0 + x) / 2 * 65535 # noqa: E731 - pack_format = 'H' - elif fmt.sampleFormat() == QAudioFormat.SampleFormat.Int16: - scaler = lambda x: x * 32767 # noqa: E731 - pack_format = 'h' + match fmt.bytesPerSample() * 8: + case 8: + match fmt.sampleFormat(): + case QAudioFormat.SampleFormat.UInt8: + scaler = lambda x: ((1.0 + x) / 2 * 255) # noqa: E731 + pack_format = 'B' + case QAudioFormat.SampleFormat.Int16: + scaler = lambda x: x * 127 # noqa: E731 + pack_format = 'b' + case 16: + little_endian = QSysInfo.Endian.ByteOrder == QSysInfo.Endian.LittleEndian + match fmt.sampleFormat(): + case QAudioFormat.SampleFormat.UInt8: + scaler = lambda x: (1.0 + x) / 2 * 65535 # noqa: E731 + pack_format = 'H' + case QAudioFormat.SampleFormat.Int16: + scaler = lambda x: x * 32767 # noqa: E731 + pack_format = 'h' assert pack_format != '' @@ -234,20 +236,21 @@ class AudioTest(QMainWindow): @Slot() def toggle_suspend_resume(self): - if self.m_audioSink.state() == QtAudio.State.SuspendedState: - qWarning("status: Suspended, resume()") - self.m_audioSink.resume() - self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) - elif self.m_audioSink.state() == QtAudio.State.ActiveState: - qWarning("status: Active, suspend()") - self.m_audioSink.suspend() - self.m_suspendResumeButton.setText(self.RESUME_LABEL) - elif self.m_audioSink.state() == QtAudio.State.StoppedState: - qWarning("status: Stopped, resume()") - self.m_audioSink.resume() - self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) - elif self.m_audioSink.state() == QtAudio.State.IdleState: - qWarning("status: IdleState") + match self.m_audioSink.state(): + case QtAudio.State.SuspendedState: + qWarning("status: Suspended, resume()") + self.m_audioSink.resume() + self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) + case QtAudio.State.ActiveState: + qWarning("status: Active, suspend()") + self.m_audioSink.suspend() + self.m_suspendResumeButton.setText(self.RESUME_LABEL) + case QtAudio.State.StoppedState: + qWarning("status: Stopped, resume()") + self.m_audioSink.resume() + self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) + case QtAudio.State.IdleState: + qWarning("status: IdleState") state_map = { QtAudio.State.ActiveState: "ActiveState", diff --git a/examples/multimedia/camera/camera.py b/examples/multimedia/camera/camera.py index ad596c76..e1fde901 100644 --- a/examples/multimedia/camera/camera.py +++ b/examples/multimedia/camera/camera.py @@ -70,28 +70,28 @@ class Camera(QMainWindow): # camera cam_permission = QCameraPermission() - cam_permission_status = qApp.checkPermission(cam_permission) # noqa: F821 - if cam_permission_status == Qt.PermissionStatus.Undetermined: - qApp.requestPermission(cam_permission, self, self.initialize) # noqa: F821 - return - if cam_permission_status == Qt.PermissionStatus.Denied: - qWarning("Camera permission is not granted!") - return - elif cam_permission_status == Qt.PermissionStatus.Granted: - print("[AudioSource] Camera permission granted") + match qApp.checkPermission(cam_permission): # noqa: F821 + case Qt.PermissionStatus.Undetermined: + qApp.requestPermission(cam_permission, self, self.initialize) # noqa: F821 + return + case Qt.PermissionStatus.Denied: + qWarning("Camera permission is not granted!") + return + case Qt.PermissionStatus.Granted: + print("[AudioSource] Camera permission granted") # microphone microphone_permission = QMicrophonePermission() - microphone_permission_status = qApp.checkPermission(microphone_permission) # noqa: F821 - if microphone_permission_status == Qt.PermissionStatus.Undetermined: - qApp.requestPermission(microphone_permission, self, self.initialize) # noqa: F821 - return - if microphone_permission_status == Qt.PermissionStatus.Denied: - qWarning("Microphone permission is not granted!") - self.initializeErrorWindow() - return - elif microphone_permission_status == Qt.PermissionStatus.Granted: - print("[AudioSource] Microphone permission granted") + match qApp.checkPermission(microphone_permission): # noqa: F821 + case Qt.PermissionStatus.Undetermined: + qApp.requestPermission(microphone_permission, self, self.initialize) # noqa: F821, E501 + return + case Qt.PermissionStatus.Denied: + qWarning("Microphone permission is not granted!") + self.initializeErrorWindow() + return + case Qt.PermissionStatus.Granted: + print("[AudioSource] Microphone permission granted") self.m_audioInput = QAudioInput() self.m_captureSession.setAudioInput(self.m_audioInput) @@ -147,22 +147,22 @@ class Camera(QMainWindow): if event.isAutoRepeat(): return - key = event.key() - if key == Qt.Key.Key_CameraFocus: - self.displayViewfinder() - event.accept() - elif key == Qt.Key.Key_Camera: - if self.m_doImageCapture: - self.takeImage() - else: - if self.m_mediaRecorder.recorderState() == QMediaRecorder.RecordingState: - self.stop() + match event.key(): + case Qt.Key.Key_CameraFocus: + self.displayViewfinder() + event.accept() + case Qt.Key.Key_Camera: + if self.m_doImageCapture: + self.takeImage() else: - self.record() + if self.m_mediaRecorder.recorderState() == QMediaRecorder.RecordingState: + self.stop() + else: + self.record() - event.accept() - else: - super().keyPressEvent(event) + event.accept() + case _: + super().keyPressEvent(event) @Slot() def updateRecordTime(self): @@ -257,21 +257,22 @@ class Camera(QMainWindow): @Slot(QMediaRecorder.RecorderState) def updateRecorderState(self, state): - if state == QMediaRecorder.RecorderState.StoppedState: - self._ui.recordButton.setEnabled(True) - self._ui.pauseButton.setEnabled(True) - self._ui.stopButton.setEnabled(False) - self._ui.metaDataButton.setEnabled(True) - elif state == QMediaRecorder.RecorderState.PausedState: - self._ui.recordButton.setEnabled(True) - self._ui.pauseButton.setEnabled(False) - self._ui.stopButton.setEnabled(True) - self._ui.metaDataButton.setEnabled(False) - elif state == QMediaRecorder.RecorderState.RecordingState: - self._ui.recordButton.setEnabled(False) - self._ui.pauseButton.setEnabled(True) - self._ui.stopButton.setEnabled(True) - self._ui.metaDataButton.setEnabled(False) + match state: + case QMediaRecorder.RecorderState.StoppedState: + self._ui.recordButton.setEnabled(True) + self._ui.pauseButton.setEnabled(True) + self._ui.stopButton.setEnabled(False) + self._ui.metaDataButton.setEnabled(True) + case QMediaRecorder.RecorderState.PausedState: + self._ui.recordButton.setEnabled(True) + self._ui.pauseButton.setEnabled(False) + self._ui.stopButton.setEnabled(True) + self._ui.metaDataButton.setEnabled(False) + case QMediaRecorder.RecorderState.RecordingState: + self._ui.recordButton.setEnabled(False) + self._ui.pauseButton.setEnabled(True) + self._ui.stopButton.setEnabled(True) + self._ui.metaDataButton.setEnabled(False) @Slot() def displayRecorderError(self): @@ -346,16 +347,17 @@ class Camera(QMainWindow): for i in range(0, QMediaMetaData.NumMetaData): if val := self.m_metaDataDialog.m_metaDataFields[i].text(): key = QMediaMetaData.Key(i) - if key == QMediaMetaData.Key.CoverArtImage: - cover_art = QImage(val) - data.insert(key, cover_art) - elif key == QMediaMetaData.Key.ThumbnailImage: - thumbnail = QImage(val) - data.insert(key, thumbnail) - elif key == QMediaMetaData.Key.Date: - date = QDateTime.fromString(val) - data.insert(key, date) - else: - data.insert(key, val) + match key: + case QMediaMetaData.Key.CoverArtImage: + cover_art = QImage(val) + data.insert(key, cover_art) + case QMediaMetaData.Key.ThumbnailImage: + thumbnail = QImage(val) + data.insert(key, thumbnail) + case QMediaMetaData.Key.Date: + date = QDateTime.fromString(val) + data.insert(key, date) + case _: + data.insert(key, val) self.m_mediaRecorder.setMetaData(data) diff --git a/examples/multimedia/player/player.py b/examples/multimedia/player/player.py index 57e0112c..397232a3 100644 --- a/examples/multimedia/player/player.py +++ b/examples/multimedia/player/player.py @@ -121,19 +121,19 @@ class Player(QWidget): self.m_pitchCompensationButton = QPushButton("Pitch compensation", self) self.m_pitchCompensationButton.setCheckable(True) - av = self.m_player.pitchCompensationAvailability() toolTip = "" - if av == QMediaPlayer.PitchCompensationAvailability.AlwaysOn: - self.m_pitchCompensationButton.setEnabled(False) - self.m_pitchCompensationButton.setChecked(True) - toolTip = "Pitch compensation always enabled on self backend" - elif av == QMediaPlayer.PitchCompensationAvailability.Unavailable: - self.m_pitchCompensationButton.setEnabled(False) - self.m_pitchCompensationButton.setChecked(False) - toolTip = "Pitch compensation unavailable on self backend" - elif av == QMediaPlayer.PitchCompensationAvailability.Available: - self.m_pitchCompensationButton.setEnabled(True) - self.m_pitchCompensationButton.setChecked(self.m_player.pitchCompensation()) + match self.m_player.pitchCompensationAvailability(): + case QMediaPlayer.PitchCompensationAvailability.AlwaysOn: + self.m_pitchCompensationButton.setEnabled(False) + self.m_pitchCompensationButton.setChecked(True) + toolTip = "Pitch compensation always enabled on self backend" + case QMediaPlayer.PitchCompensationAvailability.Unavailable: + self.m_pitchCompensationButton.setEnabled(False) + self.m_pitchCompensationButton.setChecked(False) + toolTip = "Pitch compensation unavailable on self backend" + case QMediaPlayer.PitchCompensationAvailability.Available: + self.m_pitchCompensationButton.setEnabled(True) + self.m_pitchCompensationButton.setChecked(self.m_player.pitchCompensation()) self.m_pitchCompensationButton.setToolTip(toolTip) controlLayout.addWidget(self.m_pitchCompensationButton) @@ -352,22 +352,21 @@ class Player(QWidget): def statusChanged(self, status): self.handleCursor(status) # handle status message - if (status == QMediaPlayer.MediaStatus.NoMedia - or status == QMediaPlayer.MediaStatus.LoadedMedia): - self.setStatusInfo("") - elif status == QMediaPlayer.MediaStatus.LoadingMedia: - self.setStatusInfo("Loading...") - elif (status == QMediaPlayer.MediaStatus.BufferingMedia - or status == QMediaPlayer.MediaStatus.BufferedMedia): - progress = round(self.m_player.bufferProgress() * 100.0) - self.setStatusInfo(f"Buffering {progress}%") - elif status == QMediaPlayer.MediaStatus.StalledMedia: - progress = round(self.m_player.bufferProgress() * 100.0) - self.setStatusInfo(f"Stalled {progress}%") - elif status == QMediaPlayer.MediaStatus.EndOfMedia: - QApplication.alert(self) - elif status == QMediaPlayer.MediaStatus.InvalidMedia: - self.displayErrorMessage() + match status: + case QMediaPlayer.MediaStatus.NoMedia | QMediaPlayer.MediaStatus.LoadedMedia: + self.setStatusInfo("") + case QMediaPlayer.MediaStatus.LoadingMedia: + self.setStatusInfo("Loading...") + case QMediaPlayer.MediaStatus.BufferingMedia | QMediaPlayer.MediaStatus.BufferedMedia: + progress = round(self.m_player.bufferProgress() * 100.0) + self.setStatusInfo(f"Buffering {progress}%") + case QMediaPlayer.MediaStatus.StalledMedia: + progress = round(self.m_player.bufferProgress() * 100.0) + self.setStatusInfo(f"Stalled {progress}%") + case QMediaPlayer.MediaStatus.EndOfMedia: + QApplication.alert(self) + case QMediaPlayer.MediaStatus.InvalidMedia: + self.displayErrorMessage() def handleCursor(self, status): if (status == QMediaPlayer.MediaStatus.LoadingMedia diff --git a/examples/multimedia/player/playercontrols.py b/examples/multimedia/player/playercontrols.py index 2093e99c..b3b16761 100644 --- a/examples/multimedia/player/playercontrols.py +++ b/examples/multimedia/player/playercontrols.py @@ -92,19 +92,19 @@ class PlayerControls(QWidget): baseColor = self.palette().color(QPalette.ColorRole.Base) inactiveStyleSheet = f"background-color: {baseColor.name()}" defaultStyleSheet = "" - - if state == QMediaPlayer.PlaybackState.StoppedState: - self.m_stopButton.setStyleSheet(inactiveStyleSheet) - self.m_playButton.setStyleSheet(defaultStyleSheet) - self.m_pauseButton.setStyleSheet(defaultStyleSheet) - elif state == QMediaPlayer.PlaybackState.PlayingState: - self.m_stopButton.setStyleSheet(defaultStyleSheet) - self.m_playButton.setStyleSheet(inactiveStyleSheet) - self.m_pauseButton.setStyleSheet(defaultStyleSheet) - elif state == QMediaPlayer.PlaybackState.PausedState: - self.m_stopButton.setStyleSheet(defaultStyleSheet) - self.m_playButton.setStyleSheet(defaultStyleSheet) - self.m_pauseButton.setStyleSheet(inactiveStyleSheet) + match state: + case QMediaPlayer.PlaybackState.StoppedState: + self.m_stopButton.setStyleSheet(inactiveStyleSheet) + self.m_playButton.setStyleSheet(defaultStyleSheet) + self.m_pauseButton.setStyleSheet(defaultStyleSheet) + case QMediaPlayer.PlaybackState.PlayingState: + self.m_stopButton.setStyleSheet(defaultStyleSheet) + self.m_playButton.setStyleSheet(inactiveStyleSheet) + self.m_pauseButton.setStyleSheet(defaultStyleSheet) + case QMediaPlayer.PlaybackState.PausedState: + self.m_stopButton.setStyleSheet(defaultStyleSheet) + self.m_playButton.setStyleSheet(defaultStyleSheet) + self.m_pauseButton.setStyleSheet(inactiveStyleSheet) def volume(self): linearVolume = QtAudio.convertVolume(self.m_volumeSlider.value() / 100.0, diff --git a/examples/network/downloader/downloader.py b/examples/network/downloader/downloader.py index 97e0e3e2..048fc872 100644 --- a/examples/network/downloader/downloader.py +++ b/examples/network/downloader/downloader.py @@ -48,7 +48,7 @@ class DownloaderWidget(QWidget): #  Default http url self.link_box.setText( - "http://master.qt.io/archive/qt/6.0/6.0.1/single/qt-everywhere-src-6.0.1.zip" + "https://download.qt.io/archive/qt/6.11/6.11.0/single/qt-everywhere-src-6.11.0.zip" ) #  Default destination dir diff --git a/examples/network/fortuneclient/fortuneclient.py b/examples/network/fortuneclient/fortuneclient.py index 54ce71f4..19861b27 100644 --- a/examples/network/fortuneclient/fortuneclient.py +++ b/examples/network/fortuneclient/fortuneclient.py @@ -95,21 +95,22 @@ class Client(QDialog): self._get_fortune_button.setEnabled(True) def display_error(self, socketError): - if socketError == QAbstractSocket.SocketError.RemoteHostClosedError: - pass - elif socketError == QAbstractSocket.SocketError.HostNotFoundError: - QMessageBox.information(self, "Fortune Client", - "The host was not found. Please check the host name and " - "port settings.") - elif socketError == QAbstractSocket.SocketError.ConnectionRefusedError: - QMessageBox.information(self, "Fortune Client", - "The connection was refused by the peer. Make sure the " - "fortune server is running, and check that the host name " - "and port settings are correct.") - else: - reason = self._tcp_socket.errorString() - QMessageBox.information(self, "Fortune Client", - f"The following error occurred: {reason}.") + match socketError: + case QAbstractSocket.SocketError.RemoteHostClosedError: + pass + case QAbstractSocket.SocketError.HostNotFoundError: + QMessageBox.information(self, "Fortune Client", + "The host was not found. Please check the host name and " + "port settings.") + case QAbstractSocket.SocketError.ConnectionRefusedError: + QMessageBox.information(self, "Fortune Client", + "The connection was refused by the peer. Make sure the " + "fortune server is running, and check that the host name " + "and port settings are correct.") + case _: + reason = self._tcp_socket.errorString() + QMessageBox.information(self, "Fortune Client", + f"The following error occurred: {reason}.") self._get_fortune_button.setEnabled(True) diff --git a/examples/qml/editingmodel/EditingModel/qmldir b/examples/qml/editingmodel/EditingModel/qmldir index 1ba31075..70ab0838 100644 --- a/examples/qml/editingmodel/EditingModel/qmldir +++ b/examples/qml/editingmodel/EditingModel/qmldir @@ -1,3 +1,4 @@ module UsingModel Main 1.0 Main.qml MovingRectangle 1.0 MovingRectangle.qml +typeinfo model.qmltypes diff --git a/examples/qml/editingmodel/model.py b/examples/qml/editingmodel/model.py index c89aea7a..a06803c6 100644 --- a/examples/qml/editingmodel/model.py +++ b/examples/qml/editingmodel/model.py @@ -32,18 +32,16 @@ class BaseModel(QAbstractListModel): return default def data(self, index, role: int): - if not self.db: - ret = None - elif not index.isValid(): - ret = None - elif role == Qt.ItemDataRole.DisplayRole: - ret = self.db[index.row()]["text"] - elif role == Qt.ItemDataRole.BackgroundRole: - ret = self.db[index.row()]["bgColor"] - elif role == self.RatioRole: - ret = self.db[index.row()]["ratio"] - else: - ret = None + ret = None + if self.db and index.isValid(): + item = self.db[index.row()] + match role: + case Qt.ItemDataRole.DisplayRole: + ret = item["text"] + case Qt.ItemDataRole.BackgroundRole: + ret = item["bgColor"] + case self.RatioRole: + ret = item["ratio"] return ret def setData(self, index, value, role): diff --git a/examples/qml/textproperties/TextProperties/qmldir b/examples/qml/textproperties/TextProperties/qmldir index 2a7c478a..c17242be 100644 --- a/examples/qml/textproperties/TextProperties/qmldir +++ b/examples/qml/textproperties/TextProperties/qmldir @@ -1,2 +1,3 @@ module Bars Main 1.0 Main.qml +typeinfo main.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/adding/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/adding/People/qmldir index a2bd9515..2e26586d 100644 --- a/examples/qml/tutorials/extending-qml-advanced/adding/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/adding/People/qmldir @@ -1,3 +1,3 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/People/qmldir index a2bd9515..04c812c5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/advanced1-Base-project/People/qmldir @@ -1,3 +1,4 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/People/qmldir index a2bd9515..04c812c5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/advanced2-Inheritance-and-coercion/People/qmldir @@ -1,3 +1,4 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/People/qmldir index a2bd9515..04c812c5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/advanced3-Default-properties/People/qmldir @@ -1,3 +1,4 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/People/qmldir index a2bd9515..04c812c5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/advanced4-Grouped-properties/People/qmldir @@ -1,3 +1,4 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/People/qmldir index a2bd9515..04c812c5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/advanced5-Attached-properties/People/qmldir @@ -1,3 +1,4 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/People/qmldir index a2bd9515..e17dd179 100644 --- a/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/advanced6-Property-value-source/People/qmldir @@ -1,3 +1,5 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo happybirthdaysong.qmltypes +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/binding/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/binding/People/qmldir index a2bd9515..e17dd179 100644 --- a/examples/qml/tutorials/extending-qml-advanced/binding/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/binding/People/qmldir @@ -1,3 +1,5 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo happybirthdaysong.qmltypes +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/methods/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/methods/People/qmldir index a2bd9515..04c812c5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/methods/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/methods/People/qmldir @@ -1,3 +1,4 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml-advanced/properties/People/qmldir b/examples/qml/tutorials/extending-qml-advanced/properties/People/qmldir index a2bd9515..04c812c5 100644 --- a/examples/qml/tutorials/extending-qml-advanced/properties/People/qmldir +++ b/examples/qml/tutorials/extending-qml-advanced/properties/People/qmldir @@ -1,3 +1,4 @@ module People -typeinfo coercion.qmltypes Main 1.0 Main.qml +typeinfo person.qmltypes +typeinfo birthdayparty.qmltypes diff --git a/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/qmldir index 78602c6b..b0c89b73 100644 --- a/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/qmldir +++ b/examples/qml/tutorials/extending-qml/chapter1-basics/Charts/qmldir @@ -1,4 +1,4 @@ module Charts -typeinfo chapter1-basics.qmltypes depends QtQuick App 254.0 App.qml +typeinfo basics.qmltypes diff --git a/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/qmldir index dad53787..e0a4ddfc 100644 --- a/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/qmldir +++ b/examples/qml/tutorials/extending-qml/chapter2-methods/Charts/qmldir @@ -1,4 +1,4 @@ module Charts -typeinfo chapter2-methods.qmltypes depends QtQuick App 254.0 App.qml +typeinfo methods.qmltypes diff --git a/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/qmldir index f2d39dd1..10b1f383 100644 --- a/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/qmldir +++ b/examples/qml/tutorials/extending-qml/chapter3-bindings/Charts/qmldir @@ -1,4 +1,4 @@ module Charts -typeinfo chapter3-bindings.qmltypes depends QtQuick App 254.0 App.qml +typeinfo bindings.qmltypes diff --git a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/qmldir index 7a7a4188..29fdf8f6 100644 --- a/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/qmldir +++ b/examples/qml/tutorials/extending-qml/chapter4-customPropertyTypes/Charts/qmldir @@ -1,4 +1,4 @@ module Charts -typeinfo chapter4-customPropertyTypes.qmltypes depends QtQuick App 254.0 App.qml +typeinfo customPropertyTypes.qmltypes diff --git a/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/qmldir b/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/qmldir index 48ec2434..da27b5ae 100644 --- a/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/qmldir +++ b/examples/qml/tutorials/extending-qml/chapter5-listproperties/Charts/qmldir @@ -1,4 +1,4 @@ module Charts -typeinfo chapter5-listproperties.qmltypes depends QtQuick App 254.0 App.qml +typeinfo listproperties.qmltypes diff --git a/examples/qml/usingmodel/UsingModel/qmldir b/examples/qml/usingmodel/UsingModel/qmldir index 27b7840a..b93c3dfd 100644 --- a/examples/qml/usingmodel/UsingModel/qmldir +++ b/examples/qml/usingmodel/UsingModel/qmldir @@ -1,2 +1,3 @@ module UsingModel Main 1.0 Main.qml +typeinfo main.qmltypes diff --git a/examples/qml/usingmodel/main.py b/examples/qml/usingmodel/main.py index f96d30fe..d1af5a11 100644 --- a/examples/qml/usingmodel/main.py +++ b/examples/qml/usingmodel/main.py @@ -42,12 +42,13 @@ class PersonModel (QAbstractListModel): def data(self, index, role): d = self._data[index.row()] - if role == Qt.ItemDataRole.DisplayRole: - return d.name - if role == Qt.ItemDataRole.DecorationRole: - return Qt.GlobalColor.black - if role == PersonModel.MyRole: - return d.myrole + match role: + case Qt.ItemDataRole.DisplayRole: + return d.name + case Qt.ItemDataRole.DecorationRole: + return Qt.GlobalColor.black + case PersonModel.MyRole: + return d.myrole return None @staticmethod diff --git a/examples/quick/customitems/painteditem/painteditemexample/qmldir b/examples/quick/customitems/painteditem/painteditemexample/qmldir index b33630ba..76d80564 100644 --- a/examples/quick/customitems/painteditem/painteditemexample/qmldir +++ b/examples/quick/customitems/painteditem/painteditemexample/qmldir @@ -1,2 +1,3 @@ module painteditemexample Main 1.0 Main.qml +typeinfo textballoon.qmltypes diff --git a/examples/quick/shared/FlickrRssModel.qml b/examples/quick/shared/FlickrRssModel.qml index 84402b60..44c280d5 100644 --- a/examples/quick/shared/FlickrRssModel.qml +++ b/examples/quick/shared/FlickrRssModel.qml @@ -6,7 +6,7 @@ import QtQuick ListModel { id: flickrImages property string tags : "" - readonly property string queryUrl : "http://api.flickr.com/services/feeds/photos_public.gne?" + readonly property string queryUrl : "https://api.flickr.com/services/feeds/photos_public.gne?" function encodeParams(x) { return encodeURIComponent(x.replace(" ",",")); diff --git a/examples/quick3d/customgeometry/CustomGeometryExample/qmldir b/examples/quick3d/customgeometry/CustomGeometryExample/qmldir index 9d54279f..2e15a53f 100644 --- a/examples/quick3d/customgeometry/CustomGeometryExample/qmldir +++ b/examples/quick3d/customgeometry/CustomGeometryExample/qmldir @@ -1,3 +1,5 @@ module CustomGeometryExample Main 1.0 Main.qml TorusMesh 1.0 TorusMesh.qml +typeinfo exampletriangle.qmltypes +typeinfo examplepoint.qmltypes diff --git a/examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir b/examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir index 7a564407..7e418938 100644 --- a/examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir +++ b/examples/quick3d/proceduraltexture/ProceduralTextureModule/qmldir @@ -1,2 +1,3 @@ module ProceduralTextureModule Main 1.0 Main.qml +typeinfo gradienttexture.qmltypes diff --git a/examples/quickcontrols/contactslist/Contact/qmldir b/examples/quickcontrols/contactslist/Contact/qmldir index 339d45a1..6c1d0d5d 100644 --- a/examples/quickcontrols/contactslist/Contact/qmldir +++ b/examples/quickcontrols/contactslist/Contact/qmldir @@ -5,3 +5,4 @@ ContactDelegate 1.0 ContactDelegate.ui.qml ContactForm 1.0 ContactForm.ui.qml ContactView 1.0 ContactView.ui.qml SectionDelegate 1.0 SectionDelegate.ui.qml +typeinfo contactmodel.qmltypes diff --git a/examples/quickcontrols/contactslist/contactmodel.py b/examples/quickcontrols/contactslist/contactmodel.py index 848ce54c..82b666fe 100644 --- a/examples/quickcontrols/contactslist/contactmodel.py +++ b/examples/quickcontrols/contactslist/contactmodel.py @@ -63,14 +63,15 @@ class ContactModel(QAbstractListModel): def data(self, index: QModelIndex, role: int): row = index.row() if row < self.rowCount(): - if role == ContactModel.ContactRole.FullNameRole: - return self.m_contacts[row].fullName - elif role == ContactModel.ContactRole.AddressRole: - return self.m_contacts[row].address - elif role == ContactModel.ContactRole.CityRole: - return self.m_contacts[row].city - elif role == ContactModel.ContactRole.NumberRole: - return self.m_contacts[row].number + match role: + case ContactModel.ContactRole.FullNameRole: + return self.m_contacts[row].fullName + case ContactModel.ContactRole.AddressRole: + return self.m_contacts[row].address + case ContactModel.ContactRole.CityRole: + return self.m_contacts[row].city + case ContactModel.ContactRole.NumberRole: + return self.m_contacts[row].number def roleNames(self): default = super().roleNames() diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml index 0d308a2a..bc6e9bc1 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qml/About.qml @@ -67,13 +67,13 @@ ApplicationWindow { + "

Qt licensed under GNU (L)GPL is appropriate for the " + "development of Qt applications provided you can comply with the terms " + "and conditions of the respective licenses.

" - + "

Please see %2 " + + "

Please see %2 " + "for an overview of Qt licensing.

" + "

Copyright (C) %3 The Qt Company Ltd and other " + "contributors.

" + "

Qt and the Qt logo are trademarks of The Qt Company Ltd.

" + "

Qt is The Qt Company Ltd product developed as an open source " - + "project. See %4 for more information.

") + + "project. See %4 for more information.

") .arg(Application.version).arg("qt.io/licensing").arg("2023").arg("qt.io") color: Colors.textFile wrapMode: Text.WordWrap diff --git a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir index f94e68a8..833255fa 100644 --- a/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir +++ b/examples/quickcontrols/filesystemexplorer/FileSystemModule/qmldir @@ -10,3 +10,4 @@ singleton Colors 1.0 qml/Colors.qml ResizeButton 1.0 qml/ResizeButton.qml FileSystemView 1.0 qml/FileSystemView.qml WindowDragHandler 1.0 qml/WindowDragHandler.qml +typeinfo editormodels.qmltypes diff --git a/examples/quickcontrols/gallery/gallery.pyproject b/examples/quickcontrols/gallery/gallery.pyproject deleted file mode 100644 index f285c24b..00000000 --- a/examples/quickcontrols/gallery/gallery.pyproject +++ /dev/null @@ -1,44 +0,0 @@ -{ - "files": ["gallery.py", - "gallery.qml", - "gallery.qrc", - "qmldir", - "qtquickcontrols2.conf", - "ToolBar.qml", - "+Material/ToolBar.qml", - "pages/BusyIndicatorPage.qml", - "pages/ButtonPage.qml", - "pages/CheckBoxPage.qml", - "pages/ComboBoxPage.qml", - "pages/DelayButtonPage.qml", - "pages/DelegatePage.qml", - "pages/DialogPage.qml", - "pages/DialPage.qml", - "pages/FramePage.qml", - "pages/GalleryConfig.qml", - "pages/GroupBoxPage.qml", - "pages/MenuBarPage.qml", - "pages/MonthGridPage.qml", - "pages/PageIndicatorPage.qml", - "pages/ProgressBarPage.qml", - "pages/RadioButtonPage.qml", - "pages/RangeSliderPage.qml", - "pages/ScrollablePage.qml", - "pages/ScrollBarPage.qml", - "pages/ScrollIndicatorPage.qml", - "pages/SearchFieldPage.qml", - "pages/SliderPage.qml", - "pages/SpinBoxPage.qml", - "pages/SplitViewPage.qml", - "pages/StackViewPage.qml", - "pages/SwipeViewPage.qml", - "pages/SwitchPage.qml", - "pages/TabBarPage.qml", - "pages/TableViewPage.qml", - "pages/TextAreaPage.qml", - "pages/TextFieldPage.qml", - "pages/ToolBarPage.qml", - "pages/ToolTipPage.qml", - "pages/TreeViewPage.qml", - "pages/TumblerPage.qml"] -} diff --git a/examples/quickcontrols/gallery/gallery.qml b/examples/quickcontrols/gallery/gallery.qml index 1decfc69..643214bc 100644 --- a/examples/quickcontrols/gallery/gallery.qml +++ b/examples/quickcontrols/gallery/gallery.qml @@ -155,7 +155,7 @@ ApplicationWindow { ListElement { title: qsTr("ScrollIndicator"); source: "qrc:/pages/ScrollIndicatorPage.qml" } ListElement { title: qsTr("SearchField"); source: "qrc:/pages/SearchFieldPage.qml" } ListElement { title: qsTr("Slider"); source: "qrc:/pages/SliderPage.qml" } - ListElement { title: qsTr("SpinBox"); source: "qrc:/pages/SpinBoxPage.qml" } + ListElement { title: qsTr("Spin boxes"); source: "qrc:/pages/SpinBoxPage.qml" } ListElement { title: qsTr("SplitView"); source: "qrc:/pages/SplitViewPage.qml" } ListElement { title: qsTr("StackView"); source: "qrc:/pages/StackViewPage.qml" } ListElement { title: qsTr("SwipeView"); source: "qrc:/pages/SwipeViewPage.qml" } diff --git a/examples/quickcontrols/gallery/pages/DialPage.qml b/examples/quickcontrols/gallery/pages/DialPage.qml index 2f6d636e..f4520798 100644 --- a/examples/quickcontrols/gallery/pages/DialPage.qml +++ b/examples/quickcontrols/gallery/pages/DialPage.qml @@ -23,7 +23,7 @@ ScrollablePage { enabled: !GalleryConfig.disabled value: 0.5 anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("demo dial") + Accessible.name: qsTr("Demo dial") } } } diff --git a/examples/quickcontrols/gallery/pages/MonthGridPage.qml b/examples/quickcontrols/gallery/pages/MonthGridPage.qml index bd99967b..9958b298 100644 --- a/examples/quickcontrols/gallery/pages/MonthGridPage.qml +++ b/examples/quickcontrols/gallery/pages/MonthGridPage.qml @@ -79,6 +79,7 @@ Page { locale: monthGrid.locale Layout.fillWidth: true Layout.column: 1 + Accessible.name: qsTr("Week days") } WeekNumberColumn { @@ -86,6 +87,7 @@ Page { year: monthGrid.year month: monthGrid.month Layout.fillHeight: true + Accessible.name: qsTr("Week numbers") } MonthGrid { @@ -95,6 +97,7 @@ Page { month: currentDate.getMonth() readonly property date currentDate: new Date() Layout.fillWidth: true + Accessible.name: qsTr("A grid displaying all the days in a month") } } } diff --git a/examples/quickcontrols/gallery/pages/ProgressBarPage.qml b/examples/quickcontrols/gallery/pages/ProgressBarPage.qml index 48c0e2e2..fde3c61a 100644 --- a/examples/quickcontrols/gallery/pages/ProgressBarPage.qml +++ b/examples/quickcontrols/gallery/pages/ProgressBarPage.qml @@ -23,13 +23,13 @@ ScrollablePage { id: bar value: 0.5 anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("demo progressbar") + Accessible.name: qsTr("Demo progressbar") } ProgressBar { indeterminate: true anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("indeterminate progressbar") + Accessible.name: qsTr("Indeterminate progressbar") } } } diff --git a/examples/quickcontrols/gallery/pages/RangeSliderPage.qml b/examples/quickcontrols/gallery/pages/RangeSliderPage.qml index 7f73ce17..a9f4b38b 100644 --- a/examples/quickcontrols/gallery/pages/RangeSliderPage.qml +++ b/examples/quickcontrols/gallery/pages/RangeSliderPage.qml @@ -23,7 +23,7 @@ ScrollablePage { first.value: 0.25 second.value: 0.75 anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("demo range slider") + Accessible.name: qsTr("Horizontal range slider") } RangeSlider { @@ -32,7 +32,7 @@ ScrollablePage { first.value: 0.25 second.value: 0.75 anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("vertical demo slider") + Accessible.name: qsTr("Vertical range slider") } } } diff --git a/examples/quickcontrols/gallery/pages/ScrollBarPage.qml b/examples/quickcontrols/gallery/pages/ScrollBarPage.qml index 04bd8c77..4c92e1bb 100644 --- a/examples/quickcontrols/gallery/pages/ScrollBarPage.qml +++ b/examples/quickcontrols/gallery/pages/ScrollBarPage.qml @@ -38,6 +38,7 @@ Flickable { rotation: 90 source: "../images/arrows.png" anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("Image of an arrow pointing in both vertical directions") } } } diff --git a/examples/quickcontrols/gallery/pages/ScrollIndicatorPage.qml b/examples/quickcontrols/gallery/pages/ScrollIndicatorPage.qml index e16d62c5..d6370513 100644 --- a/examples/quickcontrols/gallery/pages/ScrollIndicatorPage.qml +++ b/examples/quickcontrols/gallery/pages/ScrollIndicatorPage.qml @@ -32,6 +32,7 @@ Flickable { rotation: 90 source: "../images/arrows.png" anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("Image of an arrow pointing in both vertical directions") } } } diff --git a/examples/quickcontrols/gallery/pages/SearchFieldPage.qml b/examples/quickcontrols/gallery/pages/SearchFieldPage.qml index e790994b..09af261a 100644 --- a/examples/quickcontrols/gallery/pages/SearchFieldPage.qml +++ b/examples/quickcontrols/gallery/pages/SearchFieldPage.qml @@ -53,6 +53,7 @@ ScrollablePage { id: colorSearch suggestionModel: colorFilter anchors.horizontalCenter: parent.horizontalCenter + Accessible.name: qsTr("Demo searchfield") } } } diff --git a/examples/quickcontrols/gallery/pages/SliderPage.qml b/examples/quickcontrols/gallery/pages/SliderPage.qml index 97e169e8..1576c9f4 100644 --- a/examples/quickcontrols/gallery/pages/SliderPage.qml +++ b/examples/quickcontrols/gallery/pages/SliderPage.qml @@ -22,7 +22,7 @@ ScrollablePage { enabled: !GalleryConfig.disabled value: 0.5 anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("demo slider") + Accessible.name: qsTr("Horizontal slider") } Slider { @@ -30,7 +30,7 @@ ScrollablePage { orientation: Qt.Vertical value: 0.5 anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("vertical demo slider") + Accessible.name: qsTr("Vertical slider") } } } diff --git a/examples/quickcontrols/gallery/pages/SpinBoxPage.qml b/examples/quickcontrols/gallery/pages/SpinBoxPage.qml index 6c99c89e..dbbe8983 100644 --- a/examples/quickcontrols/gallery/pages/SpinBoxPage.qml +++ b/examples/quickcontrols/gallery/pages/SpinBoxPage.qml @@ -24,7 +24,23 @@ ScrollablePage { value: 50 anchors.horizontalCenter: parent.horizontalCenter editable: true - Accessible.name: qsTr("demo spinbox") + Accessible.name: qsTr("Demo spinbox") + } + + Label { + width: parent.width + wrapMode: Label.Wrap + horizontalAlignment: Qt.AlignHCenter + text: qsTr("DoubleSpinBox allows the user to choose a floating-point value.") + } + + DoubleSpinBox { + enabled: !GalleryConfig.disabled + value: 12.3 + stepSize: 0.1 + anchors.horizontalCenter: parent.horizontalCenter + editable: true + Accessible.name: qsTr("Demo spinbox for floating point numbers") } } } diff --git a/examples/quickcontrols/gallery/pages/TextAreaPage.qml b/examples/quickcontrols/gallery/pages/TextAreaPage.qml index c7d3aef7..858f2991 100644 --- a/examples/quickcontrols/gallery/pages/TextAreaPage.qml +++ b/examples/quickcontrols/gallery/pages/TextAreaPage.qml @@ -25,7 +25,7 @@ ScrollablePage { wrapMode: TextArea.Wrap text: qsTr("TextArea\n...\n...\n...") - Accessible.name: qsTr("demo text area") + Accessible.name: qsTr("Demo text area") } } } diff --git a/examples/quickcontrols/gallery/pages/TextFieldPage.qml b/examples/quickcontrols/gallery/pages/TextFieldPage.qml index 5ec946f9..fd24a9cb 100644 --- a/examples/quickcontrols/gallery/pages/TextFieldPage.qml +++ b/examples/quickcontrols/gallery/pages/TextFieldPage.qml @@ -22,7 +22,7 @@ ScrollablePage { enabled: !GalleryConfig.disabled placeholderText: qsTr("TextField") anchors.horizontalCenter: parent.horizontalCenter - Accessible.name: qsTr("demo textfield") + Accessible.name: qsTr("Demo textfield") } } } diff --git a/examples/quickcontrols/gallery/pages/ToolBarPage.qml b/examples/quickcontrols/gallery/pages/ToolBarPage.qml index b4b2cc8d..440b28ef 100644 --- a/examples/quickcontrols/gallery/pages/ToolBarPage.qml +++ b/examples/quickcontrols/gallery/pages/ToolBarPage.qml @@ -29,7 +29,10 @@ Page { ToolSeparator { } - ToolButton { text: "\u2699" } + ToolButton { + text: "\u2699" + Accessible.name: qsTr("Settings") + } } } @@ -53,6 +56,7 @@ Page { verticalAlignment: Text.AlignVCenter text: "\u2139" + Accessible.name: qsTr("Info") Layout.fillHeight: true Layout.preferredWidth: height } @@ -68,7 +72,10 @@ Page { ToolSeparator { } - ToolButton { text: "\u2630" } + ToolButton { + text: "\u2630" + Accessible.name: "Hamburger menu" + } } } } diff --git a/examples/quickcontrols/gallery/pyproject.toml b/examples/quickcontrols/gallery/pyproject.toml new file mode 100644 index 00000000..97d5cb22 --- /dev/null +++ b/examples/quickcontrols/gallery/pyproject.toml @@ -0,0 +1,50 @@ +[project] +name = "gallery" + +[tool.pyside6-rcc] +options = ["--compress-algo", "zlib"] + +[tool.pyside6-project] +files = ["gallery.py", + "gallery.qml", + "gallery.qrc", + "qmldir", + "qtquickcontrols2.conf", + "ToolBar.qml", + "+Material/ToolBar.qml", + "pages/BusyIndicatorPage.qml", + "pages/ButtonPage.qml", + "pages/CheckBoxPage.qml", + "pages/ComboBoxPage.qml", + "pages/DelayButtonPage.qml", + "pages/DelegatePage.qml", + "pages/DialogPage.qml", + "pages/DialPage.qml", + "pages/FramePage.qml", + "pages/GalleryConfig.qml", + "pages/GroupBoxPage.qml", + "pages/MenuBarPage.qml", + "pages/MonthGridPage.qml", + "pages/PageIndicatorPage.qml", + "pages/ProgressBarPage.qml", + "pages/RadioButtonPage.qml", + "pages/RangeSliderPage.qml", + "pages/ScrollablePage.qml", + "pages/ScrollBarPage.qml", + "pages/ScrollIndicatorPage.qml", + "pages/SearchFieldPage.qml", + "pages/SliderPage.qml", + "pages/SpinBoxPage.qml", + "pages/SplitViewPage.qml", + "pages/StackViewPage.qml", + "pages/SwipeViewPage.qml", + "pages/SwitchPage.qml", + "pages/TabBarPage.qml", + "pages/TableViewPage.qml", + "pages/TextAreaPage.qml", + "pages/TextFieldPage.qml", + "pages/ToolBarPage.qml", + "pages/ToolTipPage.qml", + "pages/TreeViewPage.qml", + "pages/TumblerPage.qml" +] diff --git a/examples/quickcontrols/gallery/rc_gallery.py b/examples/quickcontrols/gallery/rc_gallery.py index f092308b..d21a5767 100644 --- a/examples/quickcontrols/gallery/rc_gallery.py +++ b/examples/quickcontrols/gallery/rc_gallery.py @@ -1,204 +1,204 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.10.2 +# Created by: The Resource Compiler for Qt version 6.11.0 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore qt_resource_data = b"\ -\x00\x00\x0b\xe1\ +\x00\x00\x0b\xe7\ \x00\ -\x005Ex\x9c\xbd\x1b]W\xdb:\xf2=\xbfB7\ -\xfb\x12n\x1b\xa7\xd0m\x1f\xd2\xed\xde\x03\xa1\x1f\x9c\xa5\ -K\x0b\xf4v\xcf\xe9\xe9\x83b+\x89\x16\xc72\x92\x0c\ -\xa4-\xff}gd\xcb\xf1\x87d\xc0\xf4\xae\xce)\xb1\ -\xad\xd1\xcch4\x9a/\xa9\x93\x09\x99\x89t#\xf9r\ -\xa5\xc9h\xb6C\xf6\x9e\xed\xed\x92\xf3\x15#\x9f4\xf4\ -\xacS\x9al\xc8\xb1\x8e\x82\xc1dB\xce>\x1e\xfeg\ -|\xccC\x96(6>\x8aX\xa2\xf9\x8239%\xc5\ -\xb7S\xb6\x18\x7f\xd2c\x18\xb6f2\xe44&'\xa7\ -\xe4\xe0\xecp\xfc|<\x8bi\xa6\xd8`\x90J\xba\x5c\ -S\x83Y$\x80\xe0\x80\xad\xe8\x15\x17\x80\xe3@dI\ -4\x18p\xe8\x90\x1a\xa8\xcf\x84d\xdb\xb7O\x19\x0f/\ -\x1a\xaf\xc11\xdd\x88L\xab\xe6\xe7\x99H\xb4\x14\xb1*\ -\x91\x0d\x83!\xa1\x8a\xec\xa7\xe9`\x00\x7fb\x1eR\xcd\ -E\xf2\x85'\x91\xb8&?\x06\x04\x1a\x8f\xa6\xe4\xda|\ -0\xaf\xd7<\xd2\xab)y\xfe\xf2\x99y]1\x14\xd0\ -\x94\xbc\xd8\xcb\xdf\xaf\xb8\xe2\xf3\x98M\x89\x96\x193_\ -4\xd7\xf8~\xa9\xce\xe5h\x08\xb23\xac\x10\xcb\xcap\ -g`\xa0&\x93\xdf\xc8W!9\xcc\xdc\xb0\xf0\xcd|\ -\x95\x8cF\x22\x897$\x95\x22eRo\xc8\x5c\x88\x98\ - \xef\x92r\xfdAD\x80\xf9\xb7\xca\xb0\xd9\x8a\x85\x17\ -\x07\xe2&\x08\xf1\x81E\xe4\xe7\xcf\x82\xf9\xc00N\xfe\ -a_s\xc6=\xb4\xcd\xe7E\x96\x84\xf8\x0as\x8c\xd3\ -\xd1N!\x0el1\xd3$\xe2*\x8d\xe9\x86'\xcbb\ -*\xe45\x89\xb9\xd2\x7frv\x1d\x84\x99\x94\x80\xed(\ -\x89\xd8\x0d\xf9\xed\xf5k2\xde\xad\x0d.\xfa\x8b\x91\xff\ -\xa6k\x06\xa3[\x18\xcb!\xd8\xfe\xd8b_\xc3\xb4\xe3\ -`\xc9\xf4\xc8Ip'0\x22\x0f\xb48\x16\xd7L\xce\ -\xa8b\xc0\xfd\x94\x0c\x875\x1e2\x89,\x0fWZ\xa7\ -j:\x99D\x22\x0c.u\xc0\xc5\xe4R\x8f_N\x86\ -5\xe2O\xc8\xa8\x9b;\xc3\xe0\xf0r\x1d\x8f/\xf5%\ -.\xf08,\x16xo<\x84\xe1\x8e\x09?\x01\xed[\ -\xe9u\x94\x8atE%\x99g<\x06\x99\x9d\xe9M\xcc\ -T\x0ex\xc6\xb4\x869\xaa\xcaj\xe3\x06P\xc5\xe7\xf2\ -c\x89Gi\x09\x1d\xf0\x038\xaa\x14\xcfV\xa0\xa4a\ -\xa6+\x88\x140\xc1\x92\x90\xa9)\xf9:|\xa3\xc2\xe1\ -S2<\xa0\xe1\xc5\xf0[\x09\xc2\x12\x0a\xbb\x07\x09j\ -\xe80K\x1b\xb1\x14\xf4\xf6\x9fd\xabA\x22\xd9\x07\xc5\ -\xbc\xa2\x1a\x01\x13\xb0\x12KxDD\xfbF_\x03\xe0\ -h\xb9dr\xb4\xf3\x00~\xce4M\x22*\xa3\x7f\xb1\ -M\xf0\x1e\xf4\x1dW\x0a\x16L}sS-7\x11\xee\ -\x8c*\x99\x9c\x85\x86\xf4\xda\x9e9\ -\xf4m\xf7e\xac\xed\xe4\xfe\x02\x86\xf6\xe7\xa0\xad\xf7\xe5\ -\x88\x22p\x1f9\xdd\x0e\xdco5\xcfshLa\xc3\ -\xf3\xe4\xf6q\xab\x16E\xfc\xff\x81\xeaU\xb0\xe6\xc9\xa8\ -\x1a]?\xad\x07\xd7;dB\x9e\x93\xdf\xc9^9\xd8\ -f\x0b\xed\x18\x1c\x1b\x04x4\xee6\xa2\x1c\xb7E\x80|\x14\ -\xfeA\x8ev\x0fXZ\xfb\xd1\xbc&\xe3$[\xb0\xdb\xf4\x9c\ -\xb7\x15\xceO\xd9\x7f\xc1\x1c:8\xb7\x8c6B\x80\xf2\ -\xbby8j\x05\x04w\x92.\x1f\xc3\xfc~B\xee\xc2\ -f\x22\xce\xd6\x89\xf3\x8c\xba\xaaO9X\x9d`\xe5\x08\ -\xbb\xd6\xe1;\xf3\xae\x0d\xda}\xd6\x8e\x80|\xf6\x1c[\ -\xed\x94\x0c%0u\x98\xdc\xdb6N[L\xee8!\ -\xb4\x92u\x02\xd4\xc2\xd5\xad\xe8[\xe7\x0a\xb6\x15\xa7\x07\ -\xc5\x86\xa9\xdf\x92q\xc1\x97\x97\xd6\x02\x884\x0b'\xe3\ -\x8d5\xb1my\x00\xa5\x80\xe0$\x1a\xd5u\xfd)\x9e\ -\xf2\xc2\xd6\xc5b\xcc\x0d\x8b\xceLx\xeb\x8f\xf1\xf2\xf0\ -\xb5D\x99_\xb8\xf2\x83cs)f[\x1fms\x9f\ -\xf3\xdd\xe3\xa4\xb9=\xf8\xf6\xfez\x86\xeb\x1a\x8aX\xc8\ -3\xd8\xa6k\x87\xec\xf1\xe6\xa1X\xb3\xd2\x83\xe7\xd3\xc0\ -\xe8\x80\x83\xab\xd3\xfc;\x83h\x80A\xb0n\xa2\x16~\ -\xc5 \xb5R\xe0\xb2\x88\xda(\xbc\x15\xb0\xcc \x86\x88\ -!6t\xa2\x16\xe0\xf7\xcc\xf8\x02:\xa5\xb0\xae\x9a\x05\ --\xd8\xf2\xd2\xd2\xd7\xe1\xdb8\x03\xa9~\xe1\xc9\xe7\xa3\ -\xe7x\xbd\xe9m\xa6\xb8H\xf0\x89\x9f\x9c\xe5\xf7\x9d\x14\ -\x0f\x87\xdf\x02\x9e\x84q\x06\xdc\x8c\x9a\xa6\x02mZ{\ -\xe9\xac\xc1\xea\xd8\x014\xd3b\xb6\x95\x96\x13\xae0w\ -\x9e\x85\xc2V;\x9a\x05\x8c\xce\x0d\xda\x83;p\x0a\x17\ -wqW\xa5}\x08\xf0\x04\xbd\xe8\xafb\xc0$\xbb\x0f\ -\xe1\xe0\xd8\xb8\xc9{\xb3\x90{\xd8\xf6\xf0\xabRwL\xc1\x13f\xfa\xf9r\x07\xac\ -\x9d\xd3>\xcc\x81\xaa7\xc8\x1f?\xaf\xfbd\xe1\xa7\x0c\ -\xa2{\xa9\xcb\xf2\x9bK\xde\xa89\x90\x90\xfd\x8d\xfd}\ -\x97\xed\xbdh\xe7a\x02CG\xbd\x99z\x22d\x8c\x5c\ -\x1c12\xf9\x83\xec\x06x\x1f\xe5Y\xd0N\xbf\xff\xbf\ -Y\xed=c\x9e\x0a\xd8\xfb\xa2\xac\xe3X\x82\xfaSW\ -JX\xb9\xf8\xf4\xb8\xa4\xady\xdd\x0a\x82`\x7f&Y\ -M$[\xd9c\xd9\xf9\xa8\x1bQE\x1ec\x85d\xe6\ -\x99\xe7)\xf6vT\x09\x9a\x7fv\xe47\x95A\xf7K\ -n|\xda^\xcc\xa4z\xcb\xac^\xb7\xeb\xdc\x1f\xc5\xff\ -\x8diT\xaa\xf2[\xfbX\xd3\x07\xcf/\x95\x09(\x13\ -\xbc|\xbad\x09\x93\xc6\xdaa\x99Jn\x8bR\xdbR\ -\xd6\x9cb\xfdJt\x97\xa8\xee\xac\xb3\xe4zQ\xbf\xc7\ -\xba\xbb\xd7\xc7\x1aG\x89\xa9\xc0Q\xc9\x15L\ -I\x0b\x87\xb0v\x9f:>\xb6\xad\x08\xb6'dH%\ -D\x1b\x09\xf8\x8d\x08\xc4'\x16dM\x97\x09\xd7\x10U\ -\x13\xc51\x09\x93Os\xaf\x8b\x0f\xe0$\xc0\x09@\x1c\ -/\xff:\x19\xd6\x9fn\x07\xb7\x83\xff\x01f\x8c\xabf\ -\ +\x005Hx\x9c\xbd\x1b]s\xdb6\xf2]\xbf\x02\xd5\ +\xbd\xc8MD\xc5\xce%\x0f\xea\xe5:\xb6\x9c\x0f\xcf9\ +\x97\xc4r\x9b\x9b\xe9\xe4\x01\x22!\x09g\x8a\xa0\x01\xd0\ +\xb6\xda\xf8\xbf\xdf.HP\xfc\x00h\x99n\x0f3\xb1\ +Hb\xb1\xbbX,\xf6\x0b\xc8dBf\x22\xddJ\xbe\ +Zk2\x9a\x1d\x90\xa3\x17G\x87\xe4r\xcd\xc8\x17\x0d\ +=\x9b\x94&[r\xae\xa3`0\x99\x90\xf9\xe7\xd3\xff\ +\x8c\xcfy\xc8\x12\xc5\xc6g\x11K4_r&\xa7\xa4\ +\xf8v\xc1\x96\xe3/z\x0c\xc36L\x86\x9c\xc6\xe4\xd3\ +\x059\x99\x9f\x8e_\x8eg1\xcd\x14\x1b\x0cRIW\ +\x1bj0\x8b\x04\x10\x9c\xb05\xbd\xe1\x02p\x9c\x88,\ +\x89\x06\x03\x0e\x1dR\x03\xf5\x99\x90l\xf7\xf6%\xe3\xe1\ +U\xe358\xa7[\x91i\xd5\xfc<\x13\x89\x96\x22V\ +%\xb2a0$T\x91\xe34\x1d\x0c\xe0O\xccC\xaa\ +\xb9H\xbe\xf2$\x12\xb7\xe4\x8f\x01\x81\xc6\xa3)\xb95\ +\x1f\xcc\xeb-\x8f\xf4zJ^\xbe~a^\xd7\x0c\x05\ +4%\xaf\x8e\xf2\xf7\x1b\xae\xf8\x22fS\xa2e\xc6\xcc\ +\x17\xcd5\xbe_\xabK9\x1a\x82\xec\x0c+\xc4\xb22\ +<\x18\x18\xa8\xc9\xe4\x07\xf2\x9b\x90\x1cfnX\xf8f\ +\xbeJF#\x91\xc4[\x92J\x912\xa9\xb7d!D\ +L\x90wI\xb9\xfe(\x22\xc0\xfcCe\xd8l\xcd\xc2\ +\xab\x13q\x17\x84\xf8\xc0\x22\xf2\xfd{\xc1|`\x18'\ +\xff\xb0\xaf9\xe3\x1e\xda\xe6\xf32KB|\x859\xc6\ +\xe9\xe8\xa0\x10\x07\xb6\x98i\x12q\x95\xc6t\xcb\x93U\ +1\x15\xf2\x86\xc4\x5c\xe9_9\xbb\x0d\xc2LJ\xc0v\ +\x96D\xec\x8e\xfc\xf0\xe6\x0d\x19\x1f\xd6\x06\x17\xfd\xc5\xc8\ +\x7f\xd3\x0d\x83\xd1-\x8c\xe5\x10l?\xef\xb0o`\xda\ +q\xb0bz\xe4$x\x10\x18\x91\x07Z\x9c\x8b[&\ +gT1\xe0~J\x86\xc3\x1a\x0f\x99D\x96\x87k\xad\ +S5\x9dL\x22\x11\x06\xd7:\xe0br\xad\xc7\xaf'\ +\xc3\x1a\xf1gd\xd4\xcd\x9dapx\xbd\x89\xc7\xd7\xfa\ +\x1a\x17x\x1c\x16\x0b|4\x1e\xc2p\xc7\x84\x9f\x81\xf6\ +\xad\xf5&\x1e6\x11\x01\xa7m$\x80\x1a\xa6\x9d\xc1\xb4\ +\xcc\x98\x83\x9f\xcaQ_t\x00\xba\x91\xfc\x22\xe3\xb7w\ +\x9a\xc9\x84\xc6\xf1v\x04\x93;0\x10\xf7\x83B\x91\x00\ +\xa1\x04}(\x15\xe9\x86J\xb2\xc8x\x0c2\x9b\xebm\ +\xccT\x0e8gZ\xc3\x1cUe\xb5q\x03\xa8\xe2s\ +\xf9\xb1\xc4\xa3\xb4\x84\x0e\xf8\x01\x1cU\x8a\xf35(i\ +\x98\xe9\x0a\x22\x05L\xb0$djJ~\x1b\xbeU\xe1\ +\xf09\x19\x9e\xd0\xf0j\xf8\xad\x04a\x09\x85\xdd\x83\x04\ +5t\x98\xa5\x8dX\x0az\xfbO\xb2\xd3 \x91\x1c\x83\ +b\xdeP\x8d\x80\x09X\x89\x15<\x22\xa2c\xa3\xaf\x01\ +p\xb4Z19:x\x04?sM\x93\x88\xca\xe8_\ +l\x1b|\x00}\xc7\x95\x82\x05S\xdf\xdcT\xcbM\x84\ +;\xa3J&g\xa1!\xbd6\x8f\xbbnX\xe2 \x01\ +}pN\x19\x95j\x81\x12B\xa5\x88$\x05u\x1eV\ +\x18\xba\xcc'\x8a\x0c\xfdQ\xd3\x22\xbe$#\x07\xba\x83\ +\x06\x98\x11B\x09\x96\x0a;\x97js\xef\xe9\xda\x8e6\ +\xd3',V\xcc\x81?\xe7\xda\xa8h\x03\xfb\xfd\xa0\xfe\ +\xd4%A\x91\xe2G\xf5\x91%\x99_\x80\xc3\x0dt{\ +\xe4SAP\xe5\xa5\xa0\xb9\x06;\x8b\xee\x0a|@p\ +\x09\x16\xf6\x046\xc7\x8e\x81\x0bq\x9b;\x94\xc6\xf4T\ +JC\xd0\xfd))l\xbfm4\x09A\xd9T\xb0\xe4\ +q<%)E\xb19\x01b\xb6\xd4\x1f\xa9\x5c\xf1\x04\ +\xacx\xa1RU\xdb\x0e\xcb_\x88/7\xdeS\x02\x9e\ +\x90-y\xc2\xa2A\x0d\xa1\xe19\xd3\xba&7\xdb\x8e\ +CPq\xe3\x95\x0a9\xe5\xae\xe8R\xacV1+\x08\ +\x0c\xdb\x0bO\x8d\x9c;\x95\xd7\xb6\xd2\xe99\xe6\xd0X\ +\xf2\xda\xeb9]\xb0\xd8\xc11\xae\xb8\xb1\xe1\x06\xa0\xd5\ +\xad\xd9\x1dx\xdc\x96bj\xb6\x01\x81\xb5}\x02~\x07\ +'\x8f\xbf\xa7,f8\x17p\x11\x80\x83XQ\xbc\x07\ +\x9b\xc9\xe4\xd6!\x84%\x98\x81 \xe5w,\x9e\xf3\xdf\ +Yk\xa5\xb1\xb1\x98\xa3\x176\xac\x06o\xf1\xe5\xa2\xf4\ +\xac\xd5\x06+\xce\x7f\x07t4>\x8e\xf9*\x01]\x85\ +I\x80\xf16o\x1ff\xf0\xcad[\xb2`c!(\ +q\x0d\xf9\xd53$WU\xa3|_\xf3X\xa5\x0cE\ +<\xcb\xd0\xa9=V\x0d\xfc;\xd06\x8f\x9e}\x82\xdd\ +fG\x13\xb3C\x0f\x06\xad\xb1\x88\xd6A\x1c[c\xfb\ +;a\xee\xec6+6\xca8\x0f\xd3\x9c\xb0\xa0\x99\x89\ +Z\x0a\xb9\xf9\x04\xd6\x01w\x9e\xb1\x09\x97\x22\xcd\x97\xcd\ +9\xa6e\x93ZH\x8dJ\xe6\x13\xb6.\xd4\xa1N\xb6\ +\xd5L\x93\xf5\xad\xa7\x10\x17\x8b\x95\xcbR\xdav\xfft\ +\xe6\xd0\xb7\xed\xcbX\xdb\xc9\xfd\x05\x0c\x1d/@[\xf7\ +\xe5\x88\x22p\x1f9\xdd\x0f\xdco5\xcfsjLa\ +\xc3\xf3\xe4\xf6q\xa7\x16E\xfc\xff\x91\xeau\xb0\xe1\xc9\ +\xa8\x1a]?\xaf\x07\xd7\x07dB^\x92\x1f\xc9Q9\ +\xd8f\x0b\xed\x18\x1c\x1b\x04x4\xee6\xa2\x1c\xb7<\ +n\xc9\x1b\xb7\xb5E\x03\xd8t\xfco \xfe>\xc4\x00\ +xI\xc1E\xef\x228\xa1x\xbe\xb5\xddx^\xc0\x88\ +\x9d\x8f/M\xbc\xcbO\xed\xa4s^\x18\xdff<\x12\ +\xed\xecu}\x87-E\x98)\x87\x89\xaaF\x1b\xd3f\ +\xb0\xe1t\xb05\x08\x93\x22L\x0d;\xc8\xa0\xcb\xc9`\ +\xdf\xdb\x98\xa1Q%\x7f\xd4\xb3\xb4\x93Lm\x812f\ +\x83\x02\x5c\xe3OD\x89L\x86\x18a\x5c\xcbp:I\ +\xe9\x8a\xa9I\x0d\xe83|\x0a F\x1f:t\xaf\x93\ +\x10Z]?\x05\xec\xed\x89\xdaf\x82>\xe4\xb6\xbf/\ +z\xb1Y\x88.\xf4E\x7fO\xf4\xe0\xa8\xe9\xb6[:\ +\x15\x90\xbeD\xc0\x90x\xb1C\xdf\x13\xd0\x8aU\x17b\ +\xb1\xea/\x16\x13\xbf\xa8\x0e\xa1\x18\x80\x9e\xf8\xdfIp\ +\xdc>\xdc\xa6\xb3'\xe2\xf7Rdi\x87\xba\xd8\xfe\x9e\ +\xe8\xd1\x85C\xcc\xee\xc3^t\xf7E\x0eA\xdb\xfa\xbd\ +\xe4\x91\x17\xbd\x05\xe8I\x00\x87=hnj@}\x09\ +I\xb1\x92\x10\xa3u\x88\xaa\x02\xd2\x93\xc8\x05\x8d\xb8\xe8\ +\xde\xba\x15\x90\xdeD\x92\x15\x9bc\xb0\xed\x9dI\x05\xa4\ +'\x91y(E\x1cw\x08\xab\x04x\x12\x81\x07\x97\xbe\ +\x01\xd6\x97\x18\xa32\x5c\xbf\xe3,\xf6*r\x05\xa4/\ +\x91\xce%y\xdaj\xa4K\x9c\xc7\x0dU@\xbb~y\xf1\xf8\xd3r\ +\xa9\x18\xd0\x1b\xbfj\xd7\xb5q\x0d\xf2\x03f3\xeb\xe0\ +3\xa4\x05L\xde\xb0c\x95\xb2\x10\xfcM\x9bFi#\ +\xf9\xc6\xd8\xc7k=F\xa1\x04i\xb2\x1av\xe9\x98\xef\ +\x18\xa0Z&l\x1d\x9c\xe3\xce\xbf\x81pR\x11\x8a\x15\ +T\x22\x96\xc4\x9e\x96\x12\xbd\xa6\x9a\x84\x14\xc2EF2\ +\x05VB\x0bs\xd6\x19\x01\xc8&\x8d\x19\x98]SH\ +[\xd2\x10\xc6CXi\xb1\x07\xaes\x90Br\x1b\xa3\ +r\xcay\x06`a\xb4HsE\x08\x16\x02\xb2\x9b\xb6\ +\xe9\xae\xeapY\xb3\xc6\x17/\xa4\xb4\x1aa@\xa5\xf3\ +h\xc1\xc2\xe6D\xa7\x84J\x09{\x02\x98\xd9\xef\x10\x22\ +?\xbax\xec9De\x94\xef(\xe2V\xd24W\xa1\ +\x1c\xf8+\xbcw)B\xd7\xf62szP\xe5\xf2\x99\ +\xb7\x14\xae*\xa4\xfdEo\xc5Y\xc0z\x96\xf4\x11\x87\ +_\xf5'[u6\x05!\xcfy{\xdeYv\xdd\x15\ +\x85g\x89WbF\xb5\xda\xb3=\xf3\xc0\xa2\xf3\xd1N\ +\x8b\xb7\xb5\x11\xb5\xba3\x00\xbe\xde\x01\x16\xc5\xe7Z!\ +\xd6U\x9cm\xe4\x94\xe5QG\x09\xa0\x8a\x93\xf4<\xbf\ +\x87\xe1E\xbd\xfe\xd3\x15\xf9n\x9fg \xe2J\x10\x80\ +G\xeb!x\xdc\xb6w\xb6r\x08\xcc\xfd\x02\xf0\xa0\xe6\ +\x17/\xb8\x14\xf710\xf6\xad\x8d(\xce\xf2\xc0L,\ +\xf9\x0a\xa1\xcc\x85\x82\xfcz\x09>Z\xfb\xd1\xbc)\xe3\ +$[\xb0\xdb\xf4\x9c\xf7\x15\xce/\xd8\x7f\xc1\x1c:8\ +\xb7\x8c6B\x80\xf2\xbby8k\x05\x04\x0f\x92.\x1f\ +\xc3\xfc\x8aB\xee\xc2f\x22\xce6\x89\xf3\x98\xba\xaaO\ +9X\x9d`\xe5\x14\xbb\xd6\xe1;\xf6\xae\x0d:|\xd1\ +\x8e\x80|\xf6\x1c[\xed\xa0\x0c%0u\x98\xdc\xfb6\ +N[O\xee8$\xb4\x92u\x02\xd4\xc2\xd5\x9d\xe8[\ +G\x0b\xb6\x15\x07\x08\xc5\x86\xa9_\x94q\xc1\x97\xf7\xd6\ +\x02\x884\x0b'\xe3\x8d5\xb1\xedx\x00\xa5\x80\xe0$\ +\x1a\xd5u\xfd9\x1e\xf4\xc2\xd6\xc5z\xcc\x1d\x8b\xe6&\ +\xbc\xf5\xc7xy\xf8Z\xa2\xcc\xef\x5c\xf9\xc1\xb1\xb9\x14\ +\xb3\xad\x8f\xb6\xb9\x8f\xfa\xf68ln\x0f\xbe\xdf_\xcf\ +p]C\x11\x0b9\x87m\xbaq\xc8\x1e/\x1f\x8a\x0d\ ++=x>\x0d\x8c\x0e8\xb8:\xcd\x7fg\x10\x0d0\ +\x08\xd6M\xd4\xc2o\x18\xa4V\x0a\x5c\x16Q[\x85\x17\ +\x03V\x19\xc4\x101\xc4\x86N\xd4\x02\xfc\x9e\x19_@\ +\xa7\x14\xd6U\xb3\xa0\x05[\xde[\xfam\xf8.\xce@\ +\xaa_y\xf2\xcb\xd9K\xbc\xe1\xf4.S\x5c$\xf8\xc4\ +?\xcd\xf3+O\x8a\x87\xc3o\x01O\xc28\x03nF\ +MS\x816\xad\xbdt\xd6`u\xec\x00\x9ai1\xdb\ +I\xcb\x09W\x98;\xcfBa\xab\x9d\xce\x02F\xe7\x06\ +\xed\xc1\x1d8\x85\xab\x87\xb8\xab\xd2>\x05x\x82^\xf4\ +\xcfb\xc0$\xbb\x8f\xe1\xe0\xdc\xb8\xc9\xbdY\xc8=\x9e\ +9\xca\xf0p\xc1\xee`\xc9\x959\xbc\xf5\x8a\x7fa\xfd\ +fU\xef\xc1KA\xf8\x0a\xca\xe1\x1cR\xdc\xfbd\x85\ +\xcf\x9d\xad\xb1\xf4\xddi}\xf0\x02\xa42\xa8\x7f\xf2[\ +(S\xcf#\xa3\xb0\x8a\xdbu\x85\xac\xdaB\xaaXS\ +\x0f\xa7\x9d#\x0c)\x03\x07F\x08l\xde/\xc9U\x22\ +n\xdd\x13\xad\xb6\x85d\xf4\xca\xcf}\xc9LC\xed\x1e\ +\xc7\x0c\xea\xe0\x9f\xc6IS\xff\x1e\xc7\xca\xb93\xf4\x7f\ +$/n+\x8e\x0d(\x18;\xf4\x01\x1c$\xa8\xdb\x8e\ +Kt\x0d\xfe\xed\xf2\xf0U\x8b\xdak\xc7.5\xf7|\ +\xda7\x99;\xb3\xc2\xb7\xc6\xec\x82\x17J\x22\x15\xd2\xd4\ +\xb5MK{W\xbf\xfd`\xdb\xe3oK=0\x05O\ +\x98\xe9\xe7\xcb\x1d\xb0vN\xfb4\x07\xaa^\x22\x7f\xfa\ +\xbc\xf6\xc9\xc2/\x18D\xf7R\x97\xe57\x97\xbcQs\ + !\xfb\x1b\xfb\xfb!;z\xd5\xce\xc3\x04\x86\x8ez\ +;\xf5D\xc8\x18\xb98bd\xf239\x0c\xf0J\xca\ +\x8b\xa0\x9d~\xff\x7f\xb3\xda=c\x9e\x0a\xd8\x87\xa2\xac\ +\xe3X\x82\xfaSWJX\xb9\xfb\xf4\xb4\xa4\xady\xe3\ +\x0a\x82`\x7f&YM$[\xd9c\xd9\xf9\xa4KQ\ +E\x1ec\x85d\xe6\x99\xe7)\xf6\x82T\x09\x9a\x7fv\ +\xe47\x95A\xfb%7>m/fR\xbdhV\xaf\ +\xdbu\xee\x8f\xe2\xbf\xc74*U\xf9\xc5}\xac\xe9\x83\ +\xe7\x97\xca\x04\x94\x09\xde?]\xb1\x84Ic\xed\xb0L\ +%wE\xa9])kA\xb1~%\xbaKT\x0f\xd6\ +Yr\xbd\xa8_e=<\xeac\x0d\x9e&\x9f\xb3\xc4\ +T\xe0\xa8\xe4\x0a\xa6\xa4\x85CX\x87\xcf\x1d\x1f\xdbV\ +\x04\xdb32\xa4\x12\xa2\x8d\x04\xfcF\x04\xe2\x13K\xb2\ +\xa1\xab\x84k\x88\xaa\x89\xe2\x98\x84\xc9\xe7\xb9\xd7\xc5\x07\ +p\x12\xe0\x04 \x8e\x97\x7f\x9d\x0c\xebO\xf7\x83\xfb\xc1\ +\xff\x00\x92\x99\xac~\ \x00\x00\x00`\ [\ Material]\x0aPrimar\ @@ -225,7 +225,7 @@ odule App\x0asingle\ ton GalleryConfi\ g 1.0 pages/Gall\ eryConfig.qml\x0a\ -\x00\x00\x03\x5c\ +\x00\x00\x05k\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -278,9 +278,42 @@ ontalCenter\x0a \ editable\ : true\x0a \ Accessible.na\ -me: qsTr(\x22demo s\ +me: qsTr(\x22Demo s\ pinbox\x22)\x0a \ - }\x0a }\x0a}\x0a\ + }\x0a\x0a Labe\ +l {\x0a \ +width: parent.wi\ +dth\x0a \ +wrapMode: Label.\ +Wrap\x0a \ + horizontalAlign\ +ment: Qt.AlignHC\ +enter\x0a \ + text: qsTr(\x22Do\ +ubleSpinBox allo\ +ws the user to c\ +hoose a floating\ +-point value.\x22)\x0a\ + }\x0a\x0a \ + DoubleSpinBox\ + {\x0a e\ +nabled: !Gallery\ +Config.disabled\x0a\ + valu\ +e: 12.3\x0a \ + stepSize: 0.\ +1\x0a an\ +chors.horizontal\ +Center: parent.h\ +orizontalCenter\x0a\ + edit\ +able: true\x0a \ + Accessibl\ +e.name: qsTr(\x22De\ +mo spinbox for f\ +loating point nu\ +mbers\x22)\x0a \ +}\x0a }\x0a}\x0a\ \x00\x00\x02\x8a\ /\ / Copyright (C) \ @@ -548,7 +581,7 @@ talCenter: paren\ t.horizontalCent\ er\x0a A\ ccessible.name: \ -qsTr(\x22demo progr\ +qsTr(\x22Demo progr\ essbar\x22)\x0a \ }\x0a\x0a Prog\ ressBar {\x0a \ @@ -559,11 +592,11 @@ rizontalCenter: \ parent.horizonta\ lCenter\x0a \ Accessible.n\ -ame: qsTr(\x22indet\ +ame: qsTr(\x22Indet\ erminate progres\ sbar\x22)\x0a }\ \x0a }\x0a}\x0a\ -\x00\x00\x06\x87\ +\x00\x00\x06\xbd\ /\ / Copyright (C) \ 2025 The Qt Comp\ @@ -668,8 +701,11 @@ ilter\x0a \ anchors.horizo\ ntalCenter: pare\ nt.horizontalCen\ -ter\x0a }\x0a \ - }\x0a}\x0a\ +ter\x0a \ +Accessible.name:\ + qsTr(\x22Demo sear\ +chfield\x22)\x0a \ + }\x0a }\x0a}\x0a\ \x00\x00\x03\x08\ /\ / Copyright (C) \ @@ -718,7 +754,7 @@ talCenter\x0a\x0a \ n...\x5cn...\x5cn...\x22)\ \x0a Acc\ essible.name: qs\ -Tr(\x22demo text ar\ +Tr(\x22Demo text ar\ ea\x22)\x0a }\x0a \ }\x0a}\x0a\ \x00\x00\x02\xec\ @@ -770,45 +806,49 @@ oolTip.text: qsT\ r(\x22This is a too\ l tip.\x22)\x0a \ }\x0a }\x0a}\x0a\ -\x00\x00\x02=\ +\x00\x00\x02q\ \x00\ -\x00\x07\x7fx\x9c\xd5TM\x8f\xd30\x10\xbd\xe7W\x0c\ -9\xed\x0a\x92.]\x81\xd4\xdchW\xd0JEl?\ -\xc4r\xe0\xe2\xc6\x93\xc6\xc2\xb5\x83\xed\xb4\x14\xb4\xff\x9d\ -\xb1\xd3n\xbfO\xcb\x81\xf5\xc5\x9a\xf1\x9b7\xe37\x1e\ -\xb7Z\xd0\xd3\xd5\xda\x88y\xe9\xe0\xaaw\x0d\xed\x9b\xf6\ -;\x98\x96\x08#G'\x8b\x8a\xa95\x0c\x1dO\xa3V\ -\x0b&\xf7w\xdf\x92\xa1\xc8QYL\x06\x1c\x95\x13\x85\ -@\x93\xc1\xc67\xc6\x22\x19\xb9\x84\xc2\x16hr\xc1$\ -|\x19Cwr\x97\xdc&=\xc9j\x8bQ$\x16\x95\ -6\x8e\xc8G\xb5\xc8\x7f\x1c\x99iO+g\xb4\xb4\xc7\ -\xfe![\xeb\xda\xd9(\xbags\x84?\x11\xd0\x12<\ -\x83\x8a\xcc`\xa0b3\x89\xe4y\xf5\x89I\x89fM\ -L\x85\x98\xa7\x5c\xd8p\x10\x05T\x89\x8c\xfbr\xa7Z\ -\xcb.3\x1b\x22\xbf\xc6z\xd5\xe4\xd8\xf3\xf9\xc5T^\ -jc\xd3BH\xe9\xd3\x19\xbast\x80\x188\x5c\x1c\ -\x05\xf9\xd5\xb0\x85\xb8>zq3p\xa6\xc6K\xb8\xca\ -`\x81\xc6 \x7f\x10\xdc\x95\x19U\xeac\x0e\xd0\x8f\x87\ -y\x87l\x86\xf2Lb*W\xfc&\x19\x99\xfc \xc5\ -\x5c-\xa8`\xba0\xferi\xb0\xfb=r\xa09\x09\ -[\xa2q\x22\xbf\x10\xf4\xf5B\x90#D\x06?\xed\xd4\ -\x5c\xc5\xfd\xa0m|\x1d=W\x0a\x8f\xdb\xa8p\x02;\ -\xd2\xc0\xf7q\x82\xd4\x15\xe64u\xf3\xdcq\xb7vN\ -+:kj\x8d\xbf\xd7\xed\xf7\x9dNL\xd0\x1d\xe7\x1e\ -\xf3\xb1\xaa\xdb\xfe\xe7A\x80\x81zz\x03[\xc0\xaa\xa9\ -\xb4\xf1\xa6\xc1\x82\x84\x86h\x070\xac\xfa\xac9f\x0d\ -w\xfa@\xf6\xd3\xe1\xd9f\x8d.\xb4j_\xed\xed\x0b\ -\xae\x8c^\x0a\x8e\x16\xd8\x1e\x17\xe4~\x13\x0a\x0d\x14\xa4\ -\x0b\xab*I\xaduB\xab\x84\x0aD\x88O\xa4\x87\xd7\ -\x103\xc5C eI,M\xb3pb\x89\xc1\xe3'\ -\xf2\x0d\xd8:/\x81YPl)\xe6\x81\x0dfA\x5c\ -JN\xa1\xe7I-2Ca\xf4KHN\x1cn]\ -\xf9G&\xd7@\x9f\x01\xf7ll3\x94\xa0}\xb1\x9a\ -\xeeK\x92\xbaR(\x22\xbdT\xe8\xee>\x84U\x5c\xaf\ -\xe8\xd5\xed\xf5\xb0\xa1\xf9\xb7S\xfe\xffL\x9b\x7f\xc1o\ -o;\xf1\xb3\xe7\xec\xe5\x7f9\x1fC\xa3_\xc4\x97s\ -{s\xee\xcby\x8c\xfe\x02O\xe4\x17\xf2\ -\x00\x00\x03\xb3\ +\x00\x08Ix\x9c\xe5T\xdfo\xd30\x10~\xcf_q\ +\xe4i\x13$\x1d\x9b@Z\xdf\xb6N\xd0JE\xac\xed\ +\xc4x\xe0\xc5\x8d/\x89\x85c\x07\xdb\xe9(h\xff;\ +g'\xdd\xd6\xb4\x95&\xc1\x03\x08\xbf$w\xbe\xfb|\ +\xf7\xdd\x8f\xc1\x00F\xba^\x1bQ\x94\x0e\x8eF\xc7p\ +zr\xfa\x06nJ\x84\x99\xa3\x9b\xaafj\x0dS\xc7\ +\xd3h0\x80\xc5\xf5\xd5\xe7d*2T\x16\x93\x09G\ +\xe5D.\xd0\x0c\xa1\xd3\xcd1Of.!\xb7\x0aM\ +&\x98\x84\x8fs\xb8\x5c\x5c%g\xc9H\xb2\xc6b\x14\ +\x89\xaa\xd6\xc6\x11\xf8\xac\x11\xd9\xd7\x9e\x98\x8e\xb4rF\ +K\xdb\xd7O\xd9Z7\xceF\xd15+\x10~F@\ +G\xf0!\xd4$\x06\x01\x15[J$\xcd\x8b\xf7LJ\ +4kB\xcaE\x91ra\xc3E\x14\xacJd\xdc\x87\ +{\xa3\xb5\xbcd\xa6\x03\xf2g\xae\xef\xda7\x9e\xe8\xfc\ +a*+\xb5\xb1i.\xa4\xf4\xcf\x19\xca9\xda\xb2\x98\ +8\xaczN\xfe\xb4h\xc1o\x8c\x9e\xdc!8\xd3\xe0\ +!\xbb\xda`\x8e\xc6 \xbf\x15\xdc\x95C\x8a\xd4\xfbl\ +Y\xdfo\xbf;eK\x94{\x1e\xa6p\xc5\x0f\xa2\x91\ +\xc9\x0b)\x0aUQ\xc0\x940~wi\x90\xc7#R\ +\xa0\xd9q[\xa1q\x22;\xe0\xf4\xe9\x80\x93#\x8b!\ +|\xb37\xe6(\x1e\x07n\xe3\xe3\xe8w\xa9\xf0v\x1d\ +\x0b;f=\x0e|\x1d\x17HUaNS5\xf7]\ +_6\xcei\xb5\x87\xa76\xf6\xf8Ks\xfa\xf6\xfc<\ +\xde\xb9\xbe\xc82\xb4VP\xef\xa4\x8aU\xb8\xc9r\x81\ +\xce\x09UX\xcas;\xae\xed\xbf.\x90~\x8d6\xdd\ +\x94\x05:'\xea\xa1\xa36\x06wm\xde\xad6\x0d\x12\ +$4\x92\x8f\x06\x86\xd5\x1f4\xa7x\x02vzK\xf2\ +\xc3\xe5\xde\xd2\xcf\x0e\x14\xfei\xed6\xf3P\x1b\xbd\x12\ +\x1c-\xb0'X\x90\xf9\x8fPh '\x96Y]K\ +j\x14'\xb4J(@\x84]\xee\xe0%\xc4L\xf1\xe0\ +H\xaf$\x96v\x83pb\x85A\xe3\xe7\xfb\x15\xd8&\ ++\x81YPl%\x8a\x80\x06\xcbP*z\x9c\x5c\xf7\ +\x83Zd\x86\xdch\xe7HN\x18n]\xfb\x96\x95k\ +\xa0\xd5\xc2=\x1a\xebF\x1c\xb4\x0fVS\xbeD\xa9+\ +\x85\x22\xd0C\x81>\xe6C\xb6\x8a\xeb\xbb\xae\xb6]\x0d\ +[\x98?\xbb3\xfe\x9e\xd9\xf5\xfd\xff\xfa\x8c\xfa\xff\xb9\ +\x030Q\xb9\xee5\x7f\x9b\xd1\x7f\xb6\xee\xde\x85\xb6\xf8\ +'\xd7\xdd\xd9\xc93\xd6]/\x9d\xc3\xfc\ -\xd6M\x8199\xc7\x86Q+\x9a\x06\xd0\xdd`\x0f`\ -\xd8\xdbr\x94\xa1\xaa\xf1C\x18\xb5[7d\xdb\x1b\x88\ -)\xc4V\xe5% \x8b\xf1\xc5\xc7\xaf\xbdo7\xdc(\ -\xa9s\xeb\x9cS\x85a\x8bC\x85\x12\xfc\x92U\xce\x96\ -\xbbn)\xd7f\xd5\xfa\xb8*\xd2s\xc3yY7\x9d\ -\xf2\xe7Fk\x05\x14\xeblo\x04{\xe3N\xcf!r\ -\xde\xdd$r5\x132\xc69\x09\xb1[\xc9q\xaf\xb9\ -8[\xa8b\xdc\xdcx3~\x97\xfc\xbf\x91\xfe=\xfa\ -\xbf\xfd\xdb\xdf\xb9\x7f\x8ft7\x8e_\xde\xc1\xf5\x01[\ -\xa4~\xffm6\xf0\xb2\xfc\x1e\x983[\x1f\x1b/{\ -\xfab\xfd\xbe\xed\xfa\xd6lQ+\xcd\x83\x9a\xe2\x94\xa4\ -\x17\xc1\x1d\xe2\xa3~\xfcZr\x95w\xa4\x0b\xd5\x1e}\ -\xfdp\xc9T\xf3`\xdd\x86\xaaZ\xd6\xdd5\x9e\x9e'\ -\xe1\x03\xca\xda_\xec\x9f]6UTTm\xad\xaaL\ -\xc5\xad\xd6\xcc\xab\xe6qC\x88\x9b\x0fY\xcb\xa0\xf1\x0b\ -.l\x8d^\x7f\x01\xf3e\xb7\x83\xfc\xfe\xebM\xcb\x5c\ -\xc8\xe3\xf7\x12i&\xf7T\x7f\xdc\x9c9\xaa\xcf\x09c\ -\xdf\xb5\xa0\xdb\x8cY\xa5\xa2\x02\xca\x5coAH$\xbe\ -\xe0,\xd5\xdfL\x11\xa1T\xa9\xfeH*,ZpM\ -\xef\x81Y\xb5\x98\xd8\xb1\xb6\xaa}W^-\xad\xa5\xf5\ -\x07\x08T\x18\x9d\ +\x00\x0d}x\x9c\xd5W\xddo\xdb6\x10\x7f\xd7_q\ +3P\xc0F\x22\xc5\xf1\xba\x17\xa1-\x90:X\x1b \ +\xfdH\xd2!\xdbS\xc1H'\x9b\x08Ej$\x85L\ +\x1b\xfc\xbf\xf7(9\xb6\xbe\xe8%+\xf6\xd0{\x12\xef\ +\x8b\xbc\xbb\xdf\x1d\xa9\x93\x13X\xaa\xa2\xd2|\xb5\xb60\ +]\xce`1_\xfc\x02_\xd6\x08W\x96$y\xc1d\ +\x05\x976\x8d\x82\x93\x13\xb8\xf9|\xfe{x\xc9\x13\x94\ +\x06\xc3\x8b\x14\xa5\xe5\x19G\x1d\xc3\x96w\x8dYxe\ +C2\xcbQ'\x9c\x09\xf8t\x0doo\xce\xc3\x9f\xc3\ +\xa5`\xa5\xc1 \xe0y\xa1\xb4%\xe7W%O\xee{\ +\xcbh\xa9\xa4\xd5J\x98>\xff\x92U\xaa\xb4&\x08>\ +\xb3\x15\xc2?\x01\x10\xf14\x86\x82\x96\xf5\x02%\xbb\x13\ +H\x9c\x9f\xde1!PW\xe4)\xe3\xab(\xe5\xa6\x16\ +\x04\xb5\xd6R\x892\x97[{G\xa6`\x09\x97\xab\x18\ +^\xcew\xbc\x07\x9e\xda\xb5s\xad)\xbe\xa8^\x05;\ +\xe1%\xbbC\xd1r\xe03\xe8\xc85+>\xa8\x14\xe3\ +\xc6:\xba\xa5uGa\xad4\xff\x9b\x22g\xe2L\xf0\ +\x95\xcc\xc9ML\xa1G\xf5\xea\xfd\x92\x96\xa8;\x06\x16\ +\xff\x22\x8d?\xcd\x17=\x9d| \xc3\xf5;\xcdS(\ +4\x1a\xd25\xc0 a\x02e\xca4\xe4N\x0a\xcc\xf1\ +VNGe\x90\xb2\xca\x1c\xc3\xa4\xe3pGG0!\ +\xdb\xa4\x14\xccb\x0a\x99\xd2dh\x0aL\xa8\xceI\xe3\ +\xec\x18*d\xfa\x18\x98LA(\xb7Q4\x99\xed\x9c\ +m\xf6\xa9j\x92\xddT\xae\x97\xb1]\xda\x17\xf3\x0e\x9f\ +\xc9\x84ra\xa2}B\x9a\xe0w\xc9\xed\x0b\x82\x8e\xf9\ +\xb5z\x18\xdd\xae\xb3\xe5\xe9| k\x8c\xa2\x8c\x0bq\ +\xdb\xd4\xd2\xea\x12\x83\x81\xde\xdb\xd2Z%G\x9c;\x22\ +\xc4\x0a\x9ep\xbb\xf5\xb0F\xd7Q\xa3\x9a\xff\x8a\xd51\ +\xa3\x8c*\xb2=\xd7\x98\xb8\x8d\x88W\xadz\xb4I\xc9\ +%\x9d\xf0\xde\xed<\x1e\x82\xa3DIcA\xe2\xc3\xd7\ +\x06:\xaf\x9b\xaa;\x80E\x0d'\x84S\xaf5\xcf`\ +\xba\xb7}\x05\xf3\xd9\x81\xad\x1c\xf5\x9d\xbf\x86S\xbfw\ +Ga\xb87q@\xf4*o\x00\x85\xc1go\xbf;\ +\xbd\xdf\xf1\xa8d\xc8\x1dr.,\xe6\x9e\x03\xb5 \xf8\ +\xbeF\xce\x81Z{\xe0:\xae:\x1cWmzl\xb8\ +\xa4\xee\xa6\x0b\xf9\xd8h^\x836\xce^\x9c\xc2\x8b\xc5\ +d\x161\xbd\x9a\xee\x13\xb9\x9d\x095\xe3#\xcbq\xda\ +\xcb\xf1l\x1c\x9c\x1e\xea9w\x15\x1f\xb7\x7fJ\xfe\x7f\ +\x90\xfe}\xf3\xff\xf6\xef\xd1\x93\xfb\xf7\x0du\xe3\xe2\xf9\ +\x1d<\x1c\xb0m::\xfa1\x1bx\xd3\xbd\x0f\xdc\x9e\ +\xde\xcb&\xa9\xaf>C\xf7\xdbS\xef\x9a\x03j\x9dy\ +0P\xae\ +C\x1b-\xd5\xf8\xaa\xf9\xda\x04\x9b\xe0\x1b\xddOO\xe6\ +\ \x00\x00\x07\xc4\ /\ / Copyright (C) \ @@ -1596,7 +1640,7 @@ lCenter: parent.\ horizontalCenter\ \x0a Acc\ essible.name: qs\ -Tr(\x22demo textfie\ +Tr(\x22Demo textfie\ ld\x22)\x0a }\x0a \ }\x0a}\x0a\ \x00\x00\x06S\ @@ -1834,7 +1878,7 @@ rizontalCenter: \ parent.horizonta\ lCenter\x0a \ Accessible.n\ -ame: qsTr(\x22demo \ +ame: qsTr(\x22Demo \ dial\x22)\x0a }\ \x0a }\x0a}\x0a\ \x00\x00\x05-\ @@ -2091,7 +2135,7 @@ rizontalCenter: \ parent.horizonta\ lCenter\x0a }\x0a}\x0a\ \ -\x00\x00\x05S\ +\x00\x00\x05\xb3\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -2169,6 +2213,12 @@ mages/arrows.png\ ntalCenter: pare\ nt.horizontalCen\ ter\x0a \ + Accessible.n\ +ame: qsTr(\x22Image\ + of an arrow poi\ +nting in both ve\ +rtical direction\ +s\x22)\x0a \ }\x0a }\x0a \ }\x0a\x0a ScrollBar\ .vertical: Scrol\ @@ -2179,7 +2229,7 @@ heckBox.checked \ ysOn : ScrollBar\ .AsNeeded\x0a }\x0a\ }\x0a\ -\x00\x00\x040\ +\x00\x00\x047\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -2227,27 +2277,28 @@ nter: parent.hor\ izontalCenter\x0a \ Access\ ible.name: qsTr(\ -\x22demo range slid\ -er\x22)\x0a }\x0a\x0a\ - RangeSli\ -der {\x0a \ - enabled: !Gall\ -eryConfig.disabl\ -ed\x0a o\ -rientation: Qt.V\ -ertical\x0a \ - first.value:\ - 0.25\x0a \ - second.value: \ -0.75\x0a \ - anchors.horizon\ -talCenter: paren\ -t.horizontalCent\ -er\x0a A\ -ccessible.name: \ -qsTr(\x22vertical d\ -emo slider\x22)\x0a \ - }\x0a }\x0a}\x0a\ +\x22Horizontal rang\ +e slider\x22)\x0a \ + }\x0a\x0a Ra\ +ngeSlider {\x0a \ + enabled:\ + !GalleryConfig.\ +disabled\x0a \ + orientation\ +: Qt.Vertical\x0a \ + first.\ +value: 0.25\x0a \ + second.v\ +alue: 0.75\x0a \ + anchors.h\ +orizontalCenter:\ + parent.horizont\ +alCenter\x0a \ + Accessible.\ +name: qsTr(\x22Vert\ +ical range slide\ +r\x22)\x0a }\x0a \ + }\x0a}\x0a\ \x00\x00\x05b\ /\ / Copyright (C) \ @@ -2408,7 +2459,7 @@ t: qsTr(\x22Third\x22)\ }\x0a \ }\x0a }\x0a}\x0a\ \ -\x00\x00\x04\x7f\ +\x00\x00\x04\xdf\ /\ / Copyright (C) \ 2017 The Qt Comp\ @@ -2477,6 +2528,12 @@ ows.png\x22\x0a \ .horizontalCente\ r: parent.horizo\ ntalCenter\x0a \ + Acces\ +sible.name: qsTr\ +(\x22Image of an ar\ +row pointing in \ +both vertical di\ +rections\x22)\x0a \ }\x0a \ }\x0a }\x0a\x0a Sc\ rollIndicator.ve\ @@ -5202,112 +5259,112 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x90\x00\x02\x00\x00\x00\x0c\x00\x00\x00\x0a\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xe1\ -\x00\x00\x01\x9b\xc6<\xa6)\ -\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xe5\ -\x00\x00\x01\x99-^\x9a:\ -\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x00\x0cI\ -\x00\x00\x01\x9b\xc6<(c\ +\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xe7\ +\x00\x00\x01\x9c3\x9a]\xc9\ +\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xeb\ +\x00\x00\x01\x9b\x12vW\xf6\ +\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x00\x0cO\ +\x00\x00\x01\x9c3\x9a]\xc9\ \x00\x00\x00\x5c\x00\x02\x00\x00\x00\x01\x00\x00\x00\x09\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x10\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x9b\xc6D\x84W\ -\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x01\x1f\xbb\ -\x00\x00\x01\x975l\xc7\xef\ -\x00\x00\x070\x00\x00\x00\x00\x00\x01\x00\x01\x0c%\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x07l\x00\x00\x00\x00\x00\x01\x00\x01\x1c\x09\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x06r\x00\x00\x00\x00\x00\x01\x00\x00\xd2\x7f\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x07P\x00\x00\x00\x00\x00\x01\x00\x01\x10\xe4\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x066\x00\x00\x00\x00\x00\x01\x00\x00\xab\x8c\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x06\x1e\x00\x00\x00\x00\x00\x01\x00\x00\xaaI\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x00\xf8\xd9\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xda\x9f\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x06P\x00\x00\x00\x00\x00\x01\x00\x00\xadZ\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x00\xf6\xf9\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x00\xd5>\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x06\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xd88\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x83g\ -\x00\x00\x01\x9b\xc6CC\xeb\ -\x00\x00\x03\xf8\x00\x01\x00\x00\x00\x01\x00\x00f\x89\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x04\xa8\x00\x00\x00\x00\x00\x01\x00\x00~\x10\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x003\xf0\ -\x00\x00\x01\x99.H\x8f\x00\ -\x00\x00\x02Z\x00\x00\x00\x00\x00\x01\x00\x0009\ -\x00\x00\x01\x9b\xc6CC\xeb\ -\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x10\x84\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x006l\ -\x00\x00\x01\x99.H\x8f\x00\ -\x00\x00\x03\x16\x00\x00\x00\x00\x00\x01\x00\x00M\x16\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x00x\x9b\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x9b\x9a\ -\x00\x00\x01\x99.H\x8e\xfd\ -\x00\x00\x03`\x00\x00\x00\x00\x00\x01\x00\x00X\xf5\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00EN\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x01j\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x9e\ -\x00\x00\x01\x99-e\xb2 \ -\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x00\x9fr\ -\x00\x00\x01\x99.H\x8e\xfd\ -\x00\x00\x03\xd2\x00\x00\x00\x00\x00\x01\x00\x00`2\ -\x00\x00\x01\x99.H\x8e\xfd\ -\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x00'\xfc\ -\x00\x00\x01\x9b\xc6CC\xeb\ -\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x00\xa2d\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x00\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x0d$\ -\x00\x00\x01\x9b\xc6F\x1b\xb0\ -\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00!q\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x05\x82\x00\x01\x00\x00\x00\x01\x00\x00\x95\xc9\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x13\x12\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x05N\x00\x00\x00\x00\x00\x01\x00\x00\x91F\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x03\x88\x00\x00\x00\x00\x00\x01\x00\x00Y\xb9\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x026\x00\x01\x00\x00\x00\x01\x00\x00-\xf8\ -\x00\x00\x01\x99.H\x8f\x00\ -\x00\x00\x02\x12\x00\x00\x00\x00\x00\x01\x00\x00+\x08\ -\x00\x00\x01\x99.H\x8f\x00\ -\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00n\xa5\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x00s\xd6\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xa3\ -\x00\x00\x01\x9b\xc6CC\xeb\ -\x00\x00\x04\x1e\x00\x00\x00\x00\x00\x01\x00\x00kw\ -\x00\x00\x01\x9b\xc6CC\xeb\ -\x00\x00\x03>\x00\x01\x00\x00\x00\x01\x00\x00V\xad\ -\x00\x00\x01\x99.H\x8e\xfd\ -\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00\x18\xce\ -\x00\x00\x01\x99.H\x8e\xfd\ -\x00\x00\x05\x22\x00\x00\x00\x00\x00\x01\x00\x00\x8d\x01\ -\x00\x00\x01\x99.H\x8e\xff\ -\x00\x00\x02\xc6\x00\x01\x00\x00\x00\x01\x00\x00B$\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x87\x9b\ -\x00\x00\x01\x99.H\x8e\xfe\ -\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00]v\ -\x00\x00\x01\x9b\xc6CC\xeb\ +\x00\x00\x01\x9c3\x9a]\xc9\ +\x00\x00\x00t\x00\x00\x00\x00\x00\x01\x00\x01#=\ +\x00\x00\x01\x9b\x12vW\xf4\ +\x00\x00\x070\x00\x00\x00\x00\x00\x01\x00\x01\x0f\xa7\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x07l\x00\x00\x00\x00\x00\x01\x00\x01\x1f\x8b\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x06r\x00\x00\x00\x00\x00\x01\x00\x00\xd6\x01\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x07P\x00\x00\x00\x00\x00\x01\x00\x01\x14f\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x066\x00\x00\x00\x00\x00\x01\x00\x00\xaf\x0e\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x06\x1e\x00\x00\x00\x00\x00\x01\x00\x00\xad\xcb\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x07\x0e\x00\x00\x00\x00\x00\x01\x00\x00\xfc[\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x06\xce\x00\x00\x00\x00\x00\x01\x00\x00\xde!\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x06P\x00\x00\x00\x00\x00\x01\x00\x00\xb0\xdc\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x00\xfa{\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x00\xd8\xc0\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x06\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xdb\xba\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x86\x82\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x03\xf8\x00\x01\x00\x00\x00\x01\x00\x00iD\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x04\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x80\xcb\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x006p\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x02Z\x00\x00\x00\x00\x00\x01\x00\x002\xb8\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x12\x99\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x008\xec\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x03\x16\x00\x00\x00\x00\x00\x01\x00\x00O\xd1\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x04\x80\x00\x00\x00\x00\x00\x01\x00\x00{V\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x05\xa4\x00\x00\x00\x00\x00\x01\x00\x00\x9f\x1c\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x03`\x00\x00\x00\x00\x00\x01\x00\x00[\xb0\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00H\x09\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x01j\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xb3\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x05\xca\x00\x00\x00\x00\x00\x01\x00\x00\xa2\xf4\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x03\xd2\x00\x00\x00\x00\x00\x01\x00\x00b\xed\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x00*G\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x05\xf6\x00\x00\x00\x00\x00\x01\x00\x00\xa5\xe6\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x00\xc4\x00\x00\x00\x00\x00\x01\x00\x00\x0d*\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00#\x86\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x05\x82\x00\x01\x00\x00\x00\x01\x00\x00\x99K\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x15'\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x05N\x00\x00\x00\x00\x00\x01\x00\x00\x94h\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x03\x88\x00\x00\x00\x00\x00\x01\x00\x00\x5ct\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x026\x00\x01\x00\x00\x00\x01\x00\x000C\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x02\x12\x00\x00\x00\x00\x00\x01\x00\x00-S\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00q`\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x04\x5c\x00\x00\x00\x00\x00\x01\x00\x00v\x91\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x00\x1f\xb8\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x04\x1e\x00\x00\x00\x00\x00\x01\x00\x00n2\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x03>\x00\x01\x00\x00\x00\x01\x00\x00Yh\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xe3\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x05\x22\x00\x00\x00\x00\x00\x01\x00\x00\x90#\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x02\xc6\x00\x01\x00\x00\x00\x01\x00\x00D\xa4\ +\x00\x00\x01\x9c3\x9d\x8d^\ +\x00\x00\x04\xfc\x00\x00\x00\x00\x00\x01\x00\x00\x8a\xbd\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00`1\ +\x00\x00\x01\x9c3\x9d\x8d^\ \x00\x00\x07\x8c\x00\x02\x00\x00\x00\x05\x00\x00\x00:\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x07\xb4\x00\x02\x00\x00\x00\x03\x00\x00\x00H\ @@ -5318,32 +5375,32 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x07\xc4\x00\x02\x00\x00\x00\x03\x00\x00\x00?\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x07\xd8\x00\x00\x00\x00\x00\x01\x00\x01 ~\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01#\xbe\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01%?\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01$\xb8\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01!\x8c\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01\x22\xf9\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01\x22s\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01'\xb6\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01(\xf4\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01(r\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01&\x22\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01'7\ -\x00\x00\x01\x975l\xc7\xf0\ -\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01&\xb8\ -\x00\x00\x01\x975l\xc7\xf0\ +\x00\x00\x07\xd8\x00\x00\x00\x00\x00\x01\x00\x01$\x00\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01'@\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01(\xc1\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01(:\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01%\x0e\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01&{\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01%\xf5\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01+8\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01,v\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01+\xf4\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x08\x00\x00\x00\x00\x00\x01\x00\x01)\xa4\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x088\x00\x00\x00\x00\x00\x01\x00\x01*\xb9\ +\x00\x00\x01\x9b\x12vW\xf5\ +\x00\x00\x08\x1e\x00\x00\x00\x00\x00\x01\x00\x01*:\ +\x00\x00\x01\x9b\x12vW\xf5\ " def qInitResources(): diff --git a/examples/quickcontrols/stylekit/StyleKitExampleModule/Main.qml b/examples/quickcontrols/stylekit/StyleKitExampleModule/Main.qml new file mode 100644 index 00000000..a9dbc341 --- /dev/null +++ b/examples/quickcontrols/stylekit/StyleKitExampleModule/Main.qml @@ -0,0 +1,408 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import Qt.labs.StyleKit +import "styles" + +ApplicationWindow { + id: app + width: 1024 + height: 800 + visible: true + title: qsTr("StyleKit") + + // Set the initial style: + StyleKit.style: hazeStyle + + // Instantiate the available styles. The user can switch between them + // at runtime, and each style provides its own set of themes. + Haze { id: hazeStyle } + Vitrum { id: vitrumStyle } + CustomDelegates { id: delegateStyle } + Plain { id: plainStyle } + + property real spacing: 10 + StyleKit.transitionsEnabled: transitionsEnabled.checked + + ScrollView { + id: scrollView + anchors.fill: parent + contentHeight: content.height + 40 + + ColumnLayout { + id: content + x: 10 + y: app.spacing * 2 + transformOrigin: Item.TopLeft + spacing: app.spacing * 2 + + GroupBox { + title: "Buttons" + RowLayout { + spacing: app.spacing + Button { + text: "Normal" + } + + Button { + text: "Checkable" + checkable: true + } + + Button { + text: "Disabled" + enabled: false + } + + Button { + text: "Flat" + flat: true + checkable: true + } + } + } + + GroupBox { + title: "CheckBoxes and RadioButtons" + GridLayout { + rowSpacing: app.spacing + columnSpacing: app.spacing + columns: 3 + + CheckBox { + text: "Mango" + checked: true + } + + CheckBox { + text: "Avocado" + } + + CheckBox { + text: "Banano" + checked: true + } + + RadioButton { + text: "Pasta" + } + + RadioButton { + text: "Lasagna" + checked: true + } + + RadioButton { + text: "Burrita" + } + } + } + + GroupBox { + title: "Text inputs" + RowLayout { + spacing: app.spacing + + TextField { + id: tf1 + placeholderText: "Potato" + } + + TextField { + id: tf2 + placeholderText: "Tomato" + } + } + } + + GroupBox { + title: "Misc" + GridLayout { + rowSpacing: app.spacing + columnSpacing: app.spacing + columns: 3 + + Switch { + checked: true + text: "Switch 1" + } + + SpinBox { + id: spinBox1 + value: 42 + } + + ComboBox { + id: comboBox1 + model: ["One", "February", "Aramis", "Winter", "Friday"] + } + } + } + + GroupBox { + title: "Sliders" + RowLayout { + spacing: app.spacing + + ColumnLayout { + Slider { + id: slider1 + from: 0 + to: 10 + value: 5 + } + + RangeSlider { + id: rangeSlider1 + from: 0 + to: 10 + first.value: 2 + second.value: 8 + } + } + + Slider { + id: slider2 + from: 0 + to: 10 + value: 2 + orientation: Qt.Vertical + } + + RangeSlider { + id: rangeSlider2 + from: 0 + to: 10 + first.value: 2 + second.value: 8 + orientation: Qt.Vertical + } + } + } + + GroupBox { + title: "Popups" + RowLayout { + spacing: app.spacing + + Button { + text: "Open Popup" + onClicked: popup.open() + } + } + } + + GroupBox { + title: "Variations" + StyleVariation.variations: ["mini"] + ColumnLayout { + spacing: app.spacing * 2 + Text { + visible: StyleKit.style === hazeStyle + text: "These controls are affected by an Instance Variation named 'mini'" + } + RowLayout { + spacing: app.spacing + + TextField { + placeholderText: "Mini zucchini" + } + + Switch { + checked: true + } + + Button { + // This button will be affected by both an "alert" and a "mini" variation + StyleVariation.variations: ["alert"] + text: "Alert!" + } + + CheckBox { + text: "Baninis" + checked: true + } + + Slider { + value: 0.5 + } + } + Frame { + Layout.preferredHeight: 120 + Layout.fillWidth: true + Column { + spacing: 20 + anchors.fill: parent + Text { + visible: StyleKit.style === hazeStyle + anchors.horizontalCenter: parent.horizontalCenter + text: "Frame also has a Type Variation that affects Button" + } + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: "Button" + } + } + } + } + } + + GroupBox { + title: "Custom controls" + RowLayout { + spacing: app.spacing + + CustomButtonImplementation {} + CustomButtonImplementation {} + } + } + } + + // Settings menu + + GroupBox { + id: menu + anchors.right: parent.right + anchors.rightMargin: 10 + contentWidth: menuContents.implicitWidth + contentHeight: menuContents.implicitHeight + title: "Settings" + y: app.spacing * 2 + + GridLayout { + id: menuContents + columns: 2 + rowSpacing: app.spacing + columnSpacing: app.spacing + + Label { text: "Style" } + ComboBox { + id: styleSelector + textRole: "text" + valueRole: "value" + currentValue: StyleKit.style + model: [ + { value: hazeStyle, text: "Haze" }, + { value: plainStyle, text: "Plain" }, + { value: vitrumStyle, text: "Vitrum" }, + { value: delegateStyle, text: "CustomDelegates" } + ] + onCurrentTextChanged: { + StyleKit.style = model[currentIndex].value; + themeSelector.currentValue = StyleKit.style.themeName + themeSelector.model = StyleKit.style.themeNames + } + Component.onCompleted: { + themeSelector.currentValue = StyleKit.style.themeName + themeSelector.model = StyleKit.style.themeNames + } + } + + Label { text: "Theme" } + ComboBox { + id: themeSelector + onCurrentTextChanged: StyleKit.style.themeName = currentText + } + + Label { text: "Radius" } + Slider { + Layout.maximumWidth: 150 + from: 0 + to: 20 + value: StyleKit.style.control.background.radius + onValueChanged: { + // Ensure we don't set the value if the style already has the same value + // set, or if that value is out-of-range WRT the slider. In both cases, + // this would lead to a binding loop. + let styleValue = StyleKit.style.control.background.radius + if (styleValue === value || styleValue < from || styleValue > to) + return + StyleKit.style.abstractButton.background.radius = value + StyleKit.style.groupBox.background.radius = value + } + } + + Label { text: "Transitions enabled" } + Switch { + id: transitionsEnabled + checked: true + } + + Label { text: "Accent color" } + ComboBox { + id: accentColor + model: ["darkseagreen", "plum", "sandybrown", "slateblue"] + onCurrentTextChanged: app.palette.accent = currentText + } + } + } + } + + Popup { + id: popup + anchors.centerIn: parent + closePolicy: Popup.NoAutoClose + popupType: Popup.Window + + ColumnLayout { + anchors.centerIn: parent + spacing: app.spacing * 2 + + Label { + text: qsTr("A Label in a Popup") + Layout.alignment: Qt.AlignHCenter + } + + Button { + text: qsTr("Close Popup") + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + onClicked: popup.close() + } + } + } + + // In addition to Qt Quick Controls, it's also possible to + // define and style your own custom controls. + + component CustomButtonImplementation : Rectangle { + implicitWidth: fancyButton.background.implicitWidth + fancyButton.leftPadding + fancyButton.rightPadding + implicitHeight: fancyButton.background.implicitHeight + fancyButton.topPadding + fancyButton.bottomPadding + radius: fancyButton.background.radius + border.color: fancyButton.background.border.color + border.width: fancyButton.background.border.width + color: fancyButton.background.color + scale: fancyButton.background.scale + + StyleReader { + id: fancyButton + controlType: hazeStyle.fancyButton + hovered: hoverHandler.hovered + pressed: tapHandler.pressed + palette: app.palette + } + + Text { + anchors.centerIn: parent + font.pixelSize: 15 + text: "Custom Button" + } + + HoverHandler { + id: hoverHandler + } + + TapHandler { + id: tapHandler + onTapped: { + // Change the background color of all controls whose + // controlType matches fancyButton.type. + let fancyButtons = StyleKit.style.theme.getControl(fancyButton.type) + if (fancyButtons) // Only the Haze style defines a fancyButton + fancyButtons.background.color = "yellowgreen" + } + } + } +} diff --git a/examples/quickcontrols/stylekit/StyleKitExampleModule/qmldir b/examples/quickcontrols/stylekit/StyleKitExampleModule/qmldir new file mode 100644 index 00000000..37289657 --- /dev/null +++ b/examples/quickcontrols/stylekit/StyleKitExampleModule/qmldir @@ -0,0 +1,2 @@ +module StyleKitExampleModule +Main 1.0 Main.qml diff --git a/examples/quickcontrols/stylekit/StyleKitExampleModule/qt.png b/examples/quickcontrols/stylekit/StyleKitExampleModule/qt.png new file mode 100644 index 00000000..a7b16ba4 Binary files /dev/null and b/examples/quickcontrols/stylekit/StyleKitExampleModule/qt.png differ diff --git a/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/CustomDelegates.qml b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/CustomDelegates.qml new file mode 100644 index 00000000..d63259d4 --- /dev/null +++ b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/CustomDelegates.qml @@ -0,0 +1,337 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Shapes +import QtQuick.Templates as T +import Qt.labs.StyleKit + +Style { + // Defining some helper types for the custom delegates further down + component Star : Shape { + id: star + ShapePath { + fillColor: star.palette.accent + scale: Qt.size(star.width, star.height) + PathMove { x: 0.50; y: 0.00 } + PathLine { x: 0.59; y: 0.35 } + PathLine { x: 0.97; y: 0.35 } + PathLine { x: 0.66; y: 0.57 } + PathLine { x: 0.78; y: 0.91 } + PathLine { x: 0.50; y: 0.70 } + PathLine { x: 0.22; y: 0.91 } + PathLine { x: 0.34; y: 0.57 } + PathLine { x: 0.03; y: 0.35 } + PathLine { x: 0.41; y: 0.35 } + PathLine { x: 0.50; y: 0.00 } + } + NumberAnimation on rotation { + loops: Animation.Infinite + from: 0 + to: 359 + duration: 20000 + } + } + + component OverlayData: QtObject { + property real overlayScale: 1 + } + + // Define custom delegates. These replace the default StyledItem + // for selected controls in the style definition below. + + component OverlayDelegate : StyledItem { + // Using StyledItem as the base type is the easiest approach when creating + // a custom delegate. A StyledItem will draw the delegate as configured by + // the style, and give you the opportunity to place your own items on top. + id: delegate + width: parent.width + height: parent.height + + Star { + width: 40 + height: 40 + anchors.centerIn: parent + anchors.verticalCenterOffset: -10 + scale: delegate.delegateStyle.data.overlayScale + Behavior on scale { + NumberAnimation { + duration: 300 + easing.type: Easing.OutBounce + } + } + } + Text { + text: "overlay" + font.pixelSize: 8 + y: -10 + } + } + + component UnderlayDelegate : Item { + // Custom delegates that don't inherit from StyledItem needs to define + // 'delegateStyle' and 'control' properties, which are assinged to by StyleKit. + // Use 'delegateStyle' to bind to style attributes like color, radius, and opacity. + // Use 'control' to access the Quick Control the owns the delegate. + id: delegate + required property DelegateStyle delegateStyle + required property QtObject control + + implicitWidth: delegateStyle.implicitWidth + implicitHeight: delegateStyle.implicitHeight + width: parent.width + height: parent.height + scale: delegateStyle.scale + rotation: delegateStyle.rotation + visible: delegateStyle.visible + + Star { + visible: delegate.control.checked + anchors.centerIn: parent + anchors.verticalCenterOffset: -1 + width: 60 + height: 60 + } + + Text { + text: "underlay" + font.pixelSize: 8 + y: -10 + } + + StyledItem { + // Embed a StyledItem to render the standard delegate on top + delegateStyle: delegate.delegateStyle + } + } + + component SliderHandle : StyledItem { + // Unlike the 'data' property, which varies per state, you can use regular + // QML properties to pass static information to a delegate. Here, 'isFirstHandle' + // distinguishes the first from the second handle in a RangeSlider, and + // 'control' gives access to the slider's current value(s). + id: sliderHandle + property bool isFirstHandle: false + required property QtObject control + + Text { + rotation: sliderHandle.control.vertical ? -90 : 0 + color: "ghostwhite" + anchors.centerIn: parent + font.pixelSize: 9 + text: { + if (sliderHandle.control instanceof T.RangeSlider) { + if (sliderHandle.isFirstHandle) + return sliderHandle.control.first.value.toFixed(0) + else + return sliderHandle.control.second.value.toFixed(0) + } + + return sliderHandle.control.value.toFixed(0) + } + } + } + + component NoiseDelegate : ShaderEffect { + // Use graphical effects in combination with StyledItem to create more + // complex delegate appearances. In this delegate, we create a noise overlay. + implicitWidth: unifiedSourceItem.implicitWidth + implicitHeight: unifiedSourceItem.implicitHeight + width: parent.width + height: parent.height + + required property DelegateStyle delegateStyle + + // The following properties are used by the shader (noise.frag) + property size sourceItemSize: Qt.size(unifiedSourceItem.width, unifiedSourceItem.height) + property color borderColor: delegateStyle.border.color + property real borderMaskEnabled: 1 + property real borderMaskThreshold: 0.001 + property real particleDensity: 0.1 + property real particleSize: 1 + property color particleColor: "black" + property Item source: ShaderEffectSource { live: true; sourceItem: unifiedSourceItem } + property real particleOpacity: 0.4 + property real time + + // Note: noise.frag is compiled to noise.qsb from CMakeLists.txt + fragmentShader: "qrc:/effects/noise.qsb" + + NumberAnimation on time { + loops: Animation.Infinite + from: 0 + to: Math.PI * 2 + duration: 1000 + } + + StyledItem { + id: unifiedSourceItem + delegateStyle: parent.delegateStyle + width: parent.width + height: parent.height + visible: false + rotation: 0.0 + scale: 1.0 + } + + Text { + text: "noise" + font.pixelSize: 8 + y: -10 + } + } + + component WavingQt : ShaderEffect { + implicitWidth: delegateStyle.implicitWidth + implicitHeight: delegateStyle.implicitHeight + visible: delegateStyle.visible + + required property DelegateStyle delegateStyle + + // The following properties are used by the shader (wave.frag) + property real amplitude: 0.04 * 0.5 + property real frequency: 20 + property real time + + NumberAnimation on time { + loops: Animation.Infinite + from: 0 + to: Math.PI * 2 + duration: 600 + } + + // Note: wave.frag is compiled to wave.qsb from CMakeLists.txt + fragmentShader: "qrc:/effects/wave.qsb" + + property Item sourceItem: ShaderEffectSource { + sourceItem: Image { + width: 40 + height: 40 + source: "qrc:/images/qt.png" + visible: false + } + } + } + + component CustomShadowDelegate : Item { + required property DelegateStyle delegateStyle + + x: delegateStyle.shadow.verticalOffset + y: delegateStyle.shadow.horizontalOffset + width: parent.width + height: parent.height + + Rectangle { + width: parent.width + height: parent.height + radius: parent.delegateStyle.radius + color: parent.delegateStyle.shadow.color + opacity: parent.delegateStyle.shadow.opacity + } + + Text { + anchors.right: parent.right + anchors.top: parent.bottom + anchors.rightMargin: 10 + font.pixelSize: 8 + text: "Custom shadow" + } + } + + // Define the style, assigning the custom delegates above to specific + // controls in place of the default StyledItem: + + applicationWindow { + background.color: "#544e52" + } + + control { + text.color: "ghostwhite" + background { + border.color: "#3d373b" + shadow.color: "#555555" + color: "#8e848a" + } + + handle { + color: "#8e848a" + border.color: Qt.darker("#544e52", 1.5) + shadow.color: "#808080" + } + + indicator { + color: Qt.darker("#8e848a", 1.6) + } + hovered.background.color: Qt.lighter("#8e848a", 1.2) + } + + button { + topPadding: 30 + background { + delegate: OverlayDelegate{} + // Use the 'data' property to pass custom, per-state information to a custom delegate + data: OverlayData { + overlayScale: 0.5 + } + } + hovered.background.data: OverlayData { + overlayScale: 1.8 + } + pressed.background.data: OverlayData { + overlayScale: 1.6 + } + checked.background.data: OverlayData { + overlayScale: 1.4 + } + } + + flatButton { + background.shadow.visible: false + } + + checkBox { + indicator.foreground { + implicitWidth: 30 + implicitHeight: 30 + margins: 4 + delegate: WavingQt {} + } + } + + radioButton { + indicator.delegate: UnderlayDelegate {} + } + + slider { + background.visible: true + // background.delegate: NoiseDelegate {} + // indicator.delegate: NoiseDelegate {} + handle.delegate: SliderHandle { isFirstHandle: true } + handle.second.delegate: SliderHandle { isFirstHandle: false } + } + + textField { + background.shadow.verticalOffset: 4 + background.shadow.horizontalOffset: 4 + background.shadow.delegate: CustomShadowDelegate {} + } + + switchControl { + background.visible: true + checked { + background.delegate: NoiseDelegate {} + indicator.foreground.delegate: NoiseDelegate {} + } + } + + comboBox { + background.implicitWidth: 200 + } + + frame { + padding: 20 + spacing: 50 + hovered.background.color: "#8e848a" + } + +} diff --git a/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Haze.qml b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Haze.qml new file mode 100644 index 00000000..3eac3684 --- /dev/null +++ b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Haze.qml @@ -0,0 +1,844 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import Qt.labs.StyleKit + +Style { + id: style + + fonts { + system { + family: "Courier New" + pointSize: 12 + } + textField.bold: true + label.bold: true + } + + control { + // 'control' is the fallback for all the controls. Any properties that are not + // overridden by a specific control underneath will be read from here instead. + leftPadding: 8 + topPadding: 2 + rightPadding: 8 + bottomPadding: 2 + + handle { + implicitWidth: 25 + implicitHeight: 25 + radius: 25 + gradient: Gradient { + GradientStop { position: 0.0; color: Qt.alpha("black", 0.0)} + GradientStop { position: 1.0; color: Qt.alpha("black", 0.2)} + } + shadow { + opacity: 0.8 + scale: 1.1 + } + } + + indicator { + foreground.margins: 2 + } + + transition: Transition { + StyleAnimation { + animateColors: true + animateBackgroundShadow: true + animateHandleShadow: true + duration: 300 + } + } + + hovered { + // For this style, we don't want to show any transitions when entering or while inside + // the 'hovered' state. This makes the control light up immediately when hovered, but + // fade out more slowly when returning to the 'normal' state. We therefore override + // 'transition' and set it to null. + transition: null + } + } + + abstractButton { + // 'abstractButton' is the fallback for all button types such as 'button', 'checkBox', + // 'radioButton', 'switch', etc. This is a good place to style the properties they all + // have in common. Any properties not set here will fall back to those defined in 'control'. + background { + implicitWidth: 100 + implicitHeight: 30 + opacity: 0.8 + radius: 8 + + shadow { + opacity: 0.8 + scale: 1.1 + } + + gradient: Gradient { + GradientStop { position: 0.0; color: Qt.alpha("black", 0.0)} + GradientStop { position: 1.0; color: Qt.alpha("black", 0.2)} + } + } + } + + button { + // Here you can override the style for a Button. The properties you set here apply only + // to a Button, not to for example a CheckBox. Any properties not set here will fall back + // to those defined in 'abstractButton'. + pressed { + background.scale: 0.95 + } + } + + flatButton { + background.shadow.visible: false + background.gradient: null + } + + checkBox { + transition: Transition { + NumberAnimation { + // Using a StyleAnimation for transitions is optional. A StyleAnimation can be + // used in parallel with other animations, or not used at all. Here we choose to use + // a NumberAnimation instead to animate the 'checked' image so that it bounces. + properties: "indicator.foreground.leftMargin, indicator.foreground.rightMargin" + + ", indicator.foreground.topMargin, indicator.foreground.bottomMargin" + easing.type: Easing.OutBounce + duration: 500 + } + } + hovered { + transition: null + indicator.foreground.margins: -15 + } + } + + comboBox { + background.implicitWidth: 200 + pressed.background.scale: 1.0 + } + + pane { + // 'pane' is the fallback for all pane based controls, such as 'frame' and 'groupBox'. + // Any properties not set here will fall back to those defined in 'control'. + spacing: 5 + padding: 20 + background { + border.width: 0 + implicitWidth: 200 + implicitHeight: 200 + shadow.visible: false + } + } + + groupBox { + background.topMargin: 20 + background.implicitHeight: 30 + text.bold: true + } + + radioButton { + indicator { + foreground { + margins: 4 + radius: 25 / 2 + border.width: 0 + } + } + } + + scrollBar { + padding: 2 + background.visible: false + } + + scrollIndicator { + padding: 0 + indicator.foreground.margins: 0 + } + + slider { + background.implicitWidth: 180 + indicator { + implicitHeight: 8 + radius: 8 + foreground { + radius: 8 + } + } + vertical { + background.implicitWidth: 150 + } + } + + spinBox { + padding: 4 + background { + implicitWidth: 100 + scale: 1 + } + indicator.implicitHeight: 24 + } + + switchControl { + indicator { + implicitWidth: 60 + implicitHeight: 30 + radius: 5 + foreground.radius: 4 + } + handle { + leftMargin: 3 + rightMargin: 3 + } + } + + textInput { + // 'textInput' is the fallback for all text based controls, such as 'textField', 'textArea', + // and 'searchField'. Any properties not set here will fall back to those defined in 'control'. + background { + implicitWidth: 200 + } + } + + // You can define one or more StyleVariations that can be enabled from the + // application using the attached 'StyleVariation.variations' property. + // Inside a variation, you list the controls that should receive alternative + // styling when the variation is active. Any properties defined in a variation + // override those set in the Style or Theme. + // + // For example, if you set "StyleVariation.variations: ['mini']" on a GroupBox + // in the application, all controls inside that GroupBox will be affected. + // Exactly which controls are impacted depends on which ones you style inside + // the variation. + StyleVariation { + name: "mini" + + control { + padding: 2 + background { + implicitHeight: 15 + } + indicator { + implicitWidth: 15 + implicitHeight: 15 + } + handle { + implicitWidth: 15 + implicitHeight: 15 + } + } + + textInput { + background.implicitWidth: 100 + } + + abstractButton.background { + implicitWidth: 60 + } + + switchControl { + background.implicitWidth: 40 + indicator.implicitWidth: 40 + indicator.implicitHeight: 20 + } + + slider { + background.implicitWidth: 100 + indicator.implicitHeight: 8 + indicator.implicitWidth: Style.Stretch + } + + pane { + padding: 20 + spacing: 5 + } + } + + // You can also set one or more StyleVariations on a control type. Unlike Instance + // variations—which apply only to specific control instances—type variations are applied + // to *all* instances of a control type without requiring the application to use attached + // properties. + // In this example, we specify that all Buttons that are children of a Frame + // should receive alternative styling, differentiating them from other Buttons. + frame { + background { + border.width: 1 + shadow.visible: true + } + variations: StyleVariation { + button.background { + radius: 0 + color: palette.accent + shadow.visible: false + } + } + } + // Because 'groupBox' falls back to 'frame', any StyleVariation applied to a frame + // is automatically inherited by a groupBox as well. Since I in this example only want the + // different styling on frames, not group boxes, I can simply unset the variation + // for group boxes. + groupBox.variations: [] + + readonly property int fancyButton: 0 + CustomControl { + // You can also define your own custom control types and pair them with custom + // implementations in your app. Here we provide a base configuration for a control + // named 'fancyButton', and then override it in the themes to apply colors. Any + // properties not set here will fall back to those defined in 'control'. + // The 'controlType' can be any number between 0 and 100000. + controlType: fancyButton + background { + implicitWidth: 120 + implicitHeight: 30 + radius: 0 + } + } + + // A style can have any number of themes. The ones assigned to 'light' and 'dark' + // will be applied according to the current system theme if 'themeName' is set to + // "System" (the default). Setting the current themeName for a style is usually done + // from the application rather than from within the style itself. + // + // Within a theme, you can override any properties that should have different values + // when the theme is applied. Typically, a style configures structural properties + // such as implicit size, padding, and radii, while a theme specifies colors. However, + // this is not a limitation — any properties can be overridden by a theme. Properties + // not set in the theme will fall back to those defined in the style. + + light: Theme { + applicationWindow { + background.color: "gainsboro" + } + + control { + background { + color: "lightgray" + border.color: "white" + shadow.color: "white" + } + + handle { + color: "lightgray" + shadow.color: "white" + border.color: "white" + } + + indicator { + color: "white" + foreground.image.color: palette.accent + } + + checked { + background.shadow.color: "white" + background.color: "blue" + } + + focused { + background.border.color: "white" + background.shadow.color: "white" + } + + hovered { + background { + color: palette.accent + border.color: "white" + shadow.color: "white" + } + handle { + shadow.color: "white" + shadow.scale: 1.6 + border.color: "lightgray" + } + } + + disabled { + background { + opacity: 0.4 + shadow.visible: false + gradient: null + } + } + } + + abstractButton { + background { + shadow.scale: 1.05 + } + hovered.background { + shadow.scale: 1.4 + color: palette.accent + } + checked { + background.color: palette.accent + } + } + + pane { + background.color: Qt.darker("gainsboro", 1.05) + } + + switchControl { + indicator.foreground.color: "white" + checked.indicator.foreground.color: palette.accent + } + + textField { + background { + shadow.scale: 0 + border.color: "darkgray" + color: "white" + } + hovered.background { + border.color: "lightgray" + shadow.scale: 1.1 + } + focused { + background.border.color: palette.accent + background.border.width: 2 + } + focused.hovered { + background.border.color: palette.accent + } + } + + StyleVariation { + name: "alert" + abstractButton.background { + color: "orchid" + border.color: "orchid" + shadow.color: "orchid" + } + } + + CustomControl { + controlType: fancyButton + background { + color: "tan" + } + } + + // In a theme, you can also configure the theme palettes. These palettes act as + // the base palettes for the entire application. The theme palettes in Qt Quick + // are a combination of colors fetched from the operating system (including the + // currently active OS theme) and any colors you override in the 'palettes' + // section below. + // + // Because of this, StyleKit styles do not bind colors to the palette by default + // (except for the accent color). Otherwise, the style’s appearance would vary + // across platforms, since each platform defines its own palette—unless you + // explicitly override all palette colors here. If you do want palette-based + // behavior, you can bind properties to palette colors, e.g.: + // 'button.text.color: palette.textColor'. + + palettes { + system.window: "gainsboro" + textField.text: "#4e4e4e" + spinBox.highlight: "lightgray" + + button { + buttonText: "black" + highlightedText: "black" + brightText: "#4e4e4e" + disabled.buttonText: "#4e4e4e" + disabled.highlightedText: "#4e4e4e" + } + } + } + + dark: Theme { + applicationWindow { + background.color: "#544e52" + } + + control { + text.color: "lightgray" + background { + border.color: "#3d373b" + shadow.color: "#555555" + color: "#8e848a" + } + + handle { + color: "#8e848a" + border.color: Qt.darker("#544e52", 1.5) + shadow.color: "#808080" + } + + indicator { + color: Qt.darker("#8e848a", 1.6) + } + + hovered { + background { + border.color: "white" + color: palette.accent + shadow.color: "white" + shadow.scale: 1.1 + shadow.blur: 20 + } + + handle { + shadow.color: "white" + } + } + } + + abstractButton { + checked { + background.color: palette.accent + } + + disabled { + background { + opacity: 0.3 + shadow.color: "transparent" + } + checked.background.color: "green" + } + + focused { + background { + border.color: "white" + shadow.color: "white" + color: "#bbbbbb" + } + } + } + + textInput { + background.color: "white" + } + + scrollBar { + background.color: "#8e848a" + indicator.foreground.color: "white" + } + + switchControl { + indicator.foreground.color: Qt.lighter("#8e848a", 1.3) + checked.indicator.foreground.color: palette.accent + } + + slider { + indicator.foreground.color: palette.accent + } + + pane { + // The controls change background color on states like hover, but panes + // should not. Override the property here to disable that behavior for panes. + background.color: Qt.lighter("#544e52", 1.3) + background.border.color: "#3d373b" + background.shadow.visible: false + } + + StyleVariation { + name: "alert" + abstractButton.background { + color: "orchid" + border.color: "orchid" + shadow.color: "orchid" + } + } + + CustomControl { + controlType: fancyButton + background { + color: "thistle" + } + } + + palettes { + system.window: "#544e52" + textField.text: "black" + spinBox.highlight: "blue" + button { + buttonText: "white" + highlightedText: "white" + brightText: "white" + disabled.buttonText: "darkgray" + disabled.highlightedText: "darkgray" + } + } + } + + // In addition to 'light' and 'dark', you can define as many themes as you want. + // You can switch between them from the application, for example using: + // 'StyleKit.style.themeName: "HighContrast"'. + + CustomTheme { + name: "HighContrast" + theme: Theme { + control { + transition: null + + background { + implicitHeight: 40 + shadow.color: "transparent" + color: "lightgray" + border.color: "black" + border.width: 2 + gradient: null + } + + indicator { + implicitWidth: 30 + implicitHeight: 30 + color: "ghostwhite" + border.color: "black" + foreground.margins: 4 + foreground.color: "black" + foreground.image.color: "ghostwhite" + } + + handle { + border.color: "black" + border.width: 2 + implicitWidth: 30 + implicitHeight: 30 + radius: 30 + gradient: null + } + + text.bold: true + + hovered { + background.border.width: 4 + indicator.border.width: 4 + handle.border.width: 4 + } + + checked { + background.border.width: 6 + } + + disabled { + background.color: "white" + } + } + + abstractButton { + background.color: "ghostwhite" + } + + textInput { + background.color: "white" + } + + slider { + indicator { + implicitWidth: 180 + implicitHeight: 12 + color: "ghostwhite" + border.width: 1 + foreground.color: "black" + } + } + + radioButton { + indicator.radius: 255 + indicator.foreground.radius: 255 + } + + switchControl { + background { + color: "ghostwhite" + border.width: 2 + } + + indicator { + radius: 16 + margins: 0 + border.width: 2 + implicitWidth: 60 + implicitHeight: 40 + foreground.color: "transparent" + } + + handle { + implicitWidth: 20 + implicitHeight: 30 + border.width: 2 + color: "white" + margins: 6 + radius: 0 + topLeftRadius: 18 + bottomLeftRadius: 18 + } + + hovered { + indicator.border.width: 4 + } + + checked { + handle { + color: "black" + radius: 0 + topRightRadius: 18 + bottomRightRadius: 18 + } + } + } + + spinBox { + indicator.color: "black" + indicator.foreground.image.color: "white" + hovered.background.border.width: 6 + } + + itemDelegate { + background.border.width: 0 + hovered.background.border.width: 2 + hovered.text.bold: true + } + + scrollBar { + background.implicitWidth: 15 + background.implicitHeight: 15 + indicator.implicitWidth: 15 + indicator.implicitHeight: 15 + background.color: "#8e848a" + background.border.width: 1 + indicator.border.width: 3 + indicator.foreground.margins: 3 + indicator.foreground.color: "lightgray" + } + + palettes { + system.window: "white" + textField.text: "black" + button { + buttonText: "black" + highlightedText: "black" + brightText: "black" + disabled.buttonText: "white" + disabled.highlightedText: "white" + } + } + } + } + + CustomTheme { + name: "Green" + theme: Theme { + applicationWindow { + background.color: "#8da28d" + } + + control { + background { + border.color: "#547454" + shadow.color: "darkseagreen" + color: "#a0c0a0" + } + + handle { + border.color: "#547454" + shadow.color: "darkseagreen" + color: "#a0c0a0" + } + + indicator { + color: "white" + border.color: "#547454" + foreground.color: "white" + } + + text { + color: "#1c261c" + bold: true + } + + hovered { + background { + color: "#ecefec" + border.color: "#ecefec" + shadow.color: "white" + } + handle { + color: "#ecefec" + border.color: "#ecefec" + shadow.color: "white" + } + } + + checked { + indicator { + foreground.color: "#678367" + foreground.image.color: "#678367" + } + } + + disabled { + background { + color: "#80a080" + shadow.color: "transparent" + } + } + } + + checkBox { + indicator.foreground.color: "transparent" + } + + comboBox { + indicator.color: "transparent" + indicator.foreground.color: "transparent" + indicator.foreground.image.color: "white" + } + + pane { + background.color: "#a0b1a0" + background.border.color: "#415a41" + background.shadow.visible: false + } + + scrollIndicator { + indicator.foreground.color: "white" + } + + spinBox { + indicator.color: "transparent" + indicator.foreground.color: "transparent" + indicator.foreground.image.color: "white" + } + + switchControl { + indicator.foreground.color: "white" + checked.indicator.foreground.color: "#678367" + } + + textInput { + background.color: "white" + } + + StyleVariation { + name: "alert" + abstractButton.background { + color: "lime" + border.color: "lime" + shadow.color: "lime" + } + } + + palettes { + system.window: "#547454" + textField.text: "green" + textField.placeholderText: "#678367" + checkBox.buttonText: "white" + button { + buttonText: "black" + highlightedText: "white" + disabled.buttonText: "lightgray" + disabled.highlightedText: "lightgray" + } + } + } + } + + CustomTheme { + name: "Empty Theme" + theme: Theme {} + } +} diff --git a/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Plain.qml b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Plain.qml new file mode 100644 index 00000000..afedf3b9 --- /dev/null +++ b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Plain.qml @@ -0,0 +1,111 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import Qt.labs.StyleKit + +Style { + control { + // We start by styling a control in its 'normal' state + leftPadding: 20 + rightPadding: 20 + + background { + border.color: palette.accent + radius: 4 + } + + handle { + color: palette.accent.lighter(1.2) + } + + hovered { + // Here we override some of the properties for the 'hovered' state. The ones + // we don't set here will fall back to be read from the 'normal' state. + background.color: palette.accent + handle.color: palette.accent.darker(1.2) + } + + hovered.pressed { + // The states can also be nested. Since 'hovered.pressed' is more specific + // than 'hovered', the former will be read first if the same property is set + // in multiple states. + background { + color: palette.accent.darker(1.2) + scale: 0.95 + } + } + + checked { + text.color: "white" + background { + color: palette.accent.darker(1.2) + scale: 0.95 + } + } + + checked.hovered { + background.color: palette.accent + } + + checked.hovered.pressed { + background.color: palette.accent.darker(1.2) + } + + transition: Transition { + ColorAnimation { + properties: "background.color" + duration: 100 + } + } + } + + abstractButton { + // After styling what is common to all the controls in the 'control' section + // above, we now override and set properties that should be specific to only + // some of the controls. + // In this style, we want to show the background for all button types, such as + // 'button', 'checkBox', 'radioButton', etc, so we set 'background.visible: true'. + // By default, the background is normally hidden for most controls. + background { + visible: true + shadow { + color: "darkgray" + horizontalOffset: 2 + verticalOffset: 2 + } + } + } + + itemDelegate { + // We don't want the menu items in a ComboBox to fade, so we override and unset + // the transition previously set for all controls in the 'control' section. + transition: null + background.color: "transparent" + hovered { + background.color: palette.accent + text.color: "white" + } + } + + popup { + // Remove padding so that item delegates span the full width + padding: 0 + } + + scrollBar { + // Hide the background, showing only the groove and handle + background.visible: false + padding: 0 + } + + pane { + // The controls change background color on states like hover, but panes + // should not. Override the property here to disable that behavior for panes. + background.color: "white" + } + + applicationWindow { + background.color: "whitesmoke" + } +} diff --git a/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Vitrum.qml b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Vitrum.qml new file mode 100644 index 00000000..5fd60d14 --- /dev/null +++ b/examples/quickcontrols/stylekit/StyleKitExampleModule/styles/Vitrum.qml @@ -0,0 +1,495 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import Qt.labs.StyleKit + +Style { + id: style + + component NoiseDelegate : ShaderEffect { + id: noiseDelegate + implicitWidth: unifiedSourceItem.implicitWidth + implicitHeight: unifiedSourceItem.implicitHeight + width: parent.width + height: parent.height + scale: delegateStyle.scale + rotation: delegateStyle.rotation + visible: delegateStyle.visible + + required property DelegateStyle delegateStyle + + readonly property bool isDarkBg: { + let bgColor = delegateStyle.color + let luminance = (0.2126 * bgColor.r) + (0.7152 * bgColor.g) + (0.0722 * bgColor.b); + return luminance < 0.5; + } + + // The following properties are used by the shader + property size sourceItemSize: Qt.size(unifiedSourceItem.width, unifiedSourceItem.height) + property color borderColor: delegateStyle.border.color + property real borderMaskEnabled: 1 + property real borderMaskThreshold: 0.001 + property real particleDensity: 0.2 + property real particleSize: 0.5 + property color particleColor: "black" + property Item source: ShaderEffectSource { live: true; sourceItem: unifiedSourceItem } + property real time: 0 + property real particleOpacity: (delegateStyle.opacity === 1 + ? (isDarkBg ? 0.15 : 0.05) + : (isDarkBg ? 0.5 : 0.1)) + + fragmentShader: "qrc:/effects/noise.qsb" + + StyledItem { + id: unifiedSourceItem + delegateStyle: noiseDelegate.delegateStyle + width: parent.width + height: parent.height + visible: false + rotation: 0.0 + scale: 1.0 + } + } + + component ColorSet : QtObject { + property color normal + property color muted + property color subtle + } + + property MyTheme myTheme: theme as MyTheme + + component MyTheme : Theme { + property ColorSet neutralBackground + property ColorSet neutralStroke + property ColorSet neutralForeground + property ColorSet accentBackground + property ColorSet accentStroke + property ColorSet accentForeground // on-accent + + property real controlHeight: 50 + property color windowColor: "#f7f7f7" + property color shadowColor: "#404040" + + palettes { + system.window: windowColor + textField { + text: neutralForeground.normal + disabled.text: neutralForeground.muted + } + + checkBox.buttonText: neutralForeground.normal + button { + buttonText: neutralForeground.normal + highlightedText: neutralForeground.normal + brightText: neutralForeground.normal //???? + disabled.buttonText: neutralForeground.subtle + disabled.highlightedText: neutralForeground.subtle + } + } + } + + Gradient { + id: faintHorizontalGradient + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: Qt.alpha("black", 0.0)} + GradientStop { position: 1.0; color: Qt.alpha("black", 0.1)} + } + + Gradient { + id: faintVerticalGradient + orientation: Gradient.Vertical + GradientStop { position: 0.0; color: Qt.alpha("black", 0.0)} + GradientStop { position: 1.0; color: Qt.alpha("black", 0.1)} + } + + Gradient { + id: strongHorizontalGradient + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: Qt.alpha("black", 0.0)} + GradientStop { position: 1.0; color: Qt.alpha("black", 0.4)} + } + + Gradient { + id: strongVerticalGradient + orientation: Gradient.Vertical + GradientStop { position: 0.0; color: Qt.alpha("black", 0.0)} + GradientStop { position: 1.0; color: Qt.alpha("black", 0.4)} + } + + applicationWindow { + background.color: myTheme.windowColor + } + + control { + leftPadding: 10 + topPadding: 5 + rightPadding: 10 + bottomPadding: 5 + + background { + implicitHeight: myTheme.controlHeight + color: myTheme.neutralBackground.normal + delegate: NoiseDelegate {} + } + + vertical { + background.gradient: faintHorizontalGradient + indicator.gradient: faintHorizontalGradient + } + + handle { + implicitWidth: 36 + implicitHeight: 36 + radius: 18 + border.width: 2 + border.color: myTheme.neutralStroke.normal + color: myTheme.accentForeground.normal + delegate: NoiseDelegate {} + } + + indicator { + implicitHeight: myTheme.controlHeight + radius: 25 + border.width: 3 + border.color: myTheme.neutralStroke.normal + color: myTheme.neutralBackground.normal + delegate: NoiseDelegate {} + foreground { + radius: 25 + gradient: strongVerticalGradient + border.width: 3 + border.color: myTheme.accentStroke.normal + color: myTheme.accentBackground.normal + delegate: NoiseDelegate {} + } + } + + hovered { + handle.border.width: 5 + } + } + + abstractButton { + background { + implicitWidth: 100 + radius: 255 + border.width: 2 + border.color: myTheme.neutralStroke.normal + color: myTheme.neutralBackground.normal + gradient: faintVerticalGradient + delegate: NoiseDelegate {} + + shadow { + opacity: 0.25 + scale: 1.05 + verticalOffset: 2.5 + horizontalOffset: 2 + color: myTheme.shadowColor + blur: 5 + } + } + + hovered { + background.color: myTheme.neutralBackground.muted + checked.background.color: myTheme.accentBackground.muted + } + + pressed { + background.scale: 0.95 + } + + checked { + background { + color: myTheme.accentBackground.normal + border.color: myTheme.accentStroke.normal + } + } + + disabled { + background { + color: myTheme.neutralBackground.subtle + border.color: myTheme.neutralStroke.subtle + shadow.color: "transparent" + } + } + } + + pane { + padding: 20 + background.delegate: null + } + + scrollIndicator { + padding: 2 + background.implicitHeight: 6 + indicator.implicitHeight: 6 + vertical { + background.implicitWidth: 6 + indicator.implicitWidth: 6 + } + } + + scrollBar { + padding: 2 + background.implicitHeight: 20 + indicator.implicitHeight: 20 + vertical { + background.implicitWidth: 20 + indicator.implicitWidth: 20 + } + } + + checkBox { + indicator { + implicitWidth: 35 + implicitHeight: 35 + radius: 4 + border.width: 1.5 + foreground { + radius: 4 + border.width: 1.5 + image.color: myTheme.accentForeground.normal + color: myTheme.accentBackground.normal + } + } + } + + radioButton { + indicator { + implicitWidth: 35 + implicitHeight: 35 + radius: width / 2 + border.width: 1.5 + foreground { + margins: 4 + radius: width / 2 + border.width: 0 + //image.color: myTheme.activeHighlight + color: myTheme.accentBackground.normal + gradient: faintVerticalGradient + } + } + checked { + indicator.border.color: myTheme.accentStroke.normal + } + } + + popup { + padding: 2 + topPadding: 20 + bottomPadding: 20 + } + + comboBox { + background { + implicitWidth: 200 + implicitHeight: myTheme.controlHeight + radius: myTheme.controlHeight / 2 + border.color: myTheme.neutralStroke.normal + color: myTheme.neutralBackground.normal + } + indicator { + implicitHeight: myTheme.controlHeight / 6 + color: "transparent" + border.width: 0 + foreground { + margins: 4 + color: "transparent" + border.width: 0 + gradient: null + image.color: myTheme.neutralStroke.subtle + } + } + } + + spinBox { + background { + radius: myTheme.controlHeight / 2 + } + indicator { + radius: 0 + color: "transparent" + border.width: 0 + foreground.gradient: null + foreground.color: "transparent" + foreground.image.color: myTheme.accentStroke.normal + foreground.border.width: 0 + foreground.implicitWidth: 20 + foreground.implicitHeight: 20 + } + } + + textField { + background { + radius: 9999999999 + implicitWidth: 200 + implicitHeight: myTheme.controlHeight + border.color: myTheme.neutralStroke.normal + color: myTheme.neutralBackground.normal + } + hovered.background.color: myTheme.neutralBackground.muted + focused.background.border.color: myTheme.accentStroke.normal + } + + slider { + spacing: 26 + background.implicitWidth: 180 + // indicator.implicitHeight: UnifiedStyle.Stretch + indicator.foreground.minimumWidth: 50 + indicator.foreground.margins: 2 + indicator.foreground.delegate: null + handle { + leftMargin: 8 + rightMargin: 8 + } + } + + switchControl { + spacing: 8 + indicator { + implicitWidth: 80 + implicitHeight: myTheme.controlHeight + foreground.visible: false + } + handle { + leftMargin: 8 + rightMargin: 8 + } + checked { + indicator { + color: myTheme.accentBackground.normal + border.color: myTheme.accentStroke.normal + gradient: strongVerticalGradient + } + } + } + + flatButton { + hovered.background.visible: true + checked.background.visible: true + hovered.background.color: myTheme.neutralBackground.muted + checked.background.color: myTheme.accentBackground.normal + hovered.checked.background.color: myTheme.accentBackground.muted + } + + itemDelegate { + hovered.background.color: myTheme.accentBackground.normal + } + + // THEMES + + light: MyTheme { + windowColor: "#EFF5F5F5" // #F5F5F5 · 92% #EF + shadowColor: "#AEAEAE" + + accentBackground: ColorSet { + normal: "#8671EC" + muted: "#B7ABF4" + subtle: "#D9D2F9" + } + accentStroke: ColorSet { + normal: "#4530B0" + muted: "#654FD4" + subtle: "#8671EC" + } + accentForeground: ColorSet { + normal: "#FFFFFF" + muted: "#909090" + subtle: "#B7ABF4" + } + + neutralBackground: ColorSet { + normal: "#FFFFFF" //#FFFFFF · 78% #C7 + muted: "#FCFCFC" //#FCFCFC · 92% #EF + subtle: "#E3E3E3" //#E3E3E3 · 94% #F0 + } + neutralStroke: ColorSet { + normal: "#CDCDCD" + muted: "#AEAEAE" + subtle: "#BEBEBE" + } + neutralForeground: ColorSet { + normal: "#000000" + muted: "#2D2D2D" + subtle: "#A9A9A9" + } + } + + dark: MyTheme { + windowColor: "#EF444444" // #222222 · 92% #EF + shadowColor: "#000" + + accentBackground: ColorSet { + normal: "#654FD4" + muted: "#4530B0" + subtle: "#361EAB" + } + accentStroke: ColorSet { + normal: "#654FD4" + muted: "#654FD4" + subtle: "#8671EC" + } + accentForeground: ColorSet { + normal: "#E0E0E0" + muted: "#A9A9A9" + subtle: "#654FD4" + } + + neutralBackground: ColorSet { + normal: "#434343" //#434343 · 78% #C7 + muted: "#636363" // #636363 · 92% #EF + subtle: "#545454" //#545454 · 94% #F0 + } + neutralStroke: ColorSet { + normal: "#A9A9A9" + muted: "#545454" + subtle: "#3B3B3B" + } + neutralForeground: ColorSet { + normal: "#FFFFFF" + muted: "#BEBEBE" + subtle: "#353535" + } + } + + CustomTheme { + name: "Green" + theme: MyTheme { + windowColor: "#f0f4fbf4" + + accentBackground: ColorSet { + normal: "green" + muted: Qt.lighter("green") + subtle: Qt.lighter("green", 2) + } + accentStroke: ColorSet { + normal: "darkgreen" + muted: Qt.lighter("darkgreen") + subtle: Qt.lighter("darkgreen", 2) + } + accentForeground: ColorSet { + normal: "#FFFFFF" + muted: "#909090" + subtle: "#B7ABF4" + } + + neutralBackground: ColorSet { + normal: "#C7EEFFEE" + muted: "#EFF0FCF0" + subtle: "#F0E0F0E0" + } + neutralStroke: ColorSet { + normal: "#CDDDCD" + muted: "#AEBEAE" + subtle: "#BECEBE" + } + neutralForeground: ColorSet { + normal: "#000000" + muted: "#2D2D2D" + subtle: "#A9A9A9" + } + } + } +} diff --git a/examples/quickcontrols/stylekit/doc/stylekit.rst b/examples/quickcontrols/stylekit/doc/stylekit.rst new file mode 100644 index 00000000..62431fe2 --- /dev/null +++ b/examples/quickcontrols/stylekit/doc/stylekit.rst @@ -0,0 +1,37 @@ +Qt Quick Controls - StyleKit +============================ + +A PySide6 application that demonstrates the analogous example in Qt +`StyleKit Example`_. + +This example shows how to style `Qt Quick Controls`_ applications using +`Qt Labs StyleKit`_\. + +It includes several styles that each demonstrate different aspects of styling: + +* ``Plain`` - A minimal style with only the basics +* ``Haze`` - An advanced style with multiple themes +* ``Vitrum`` - A style targeting VR environments +* ``CustomDelegates`` - A style demonstrating how to create overlays, underlays, and + shader effects + +The example demonstrates, among other things, how to: + +* Implement and switch between different styles. +* Implement support for light and dark themes, as well as additional themes such as high-contrast. +* Use `StyleVariation`_ to provide alternative styling for parts of the application. +* Implement custom delegates to add overlay and underlay effects. +* Apply shader-based visual effects to the controls. +* Build custom controls using `CustomControl`_ and `StyleReader`_\. +* Interact with a style at runtime, for example to change the theme or adjust style + properties like the background radius. + +.. image:: stylekit.webp + :alt: StyleKit Screenshot + +.. _`StyleKit Example`: https://doc.qt.io/qt-6/qtlabsstylekit-stylekit-example.html +.. _`Qt Quick Controls`: https://doc.qt.io/qt-6/qtquickcontrols-index.html +.. _`Qt Labs StyleKit`: https://doc.qt.io/qt-6/qtlabsstylekit-index.html +.. _StyleVariation: https://doc.qt.io/qt-6/qml-qt-labs-stylekit-stylevariation.html +.. _CustomControl: https://doc.qt.io/qt-6/qml-qt-labs-stylekit-customcontrol.html +.. _StyleReader: https://doc.qt.io/qt-6/qml-qt-labs-stylekit-stylereader.html diff --git a/examples/quickcontrols/stylekit/doc/stylekit.webp b/examples/quickcontrols/stylekit/doc/stylekit.webp new file mode 100644 index 00000000..3fe46612 Binary files /dev/null and b/examples/quickcontrols/stylekit/doc/stylekit.webp differ diff --git a/examples/quickcontrols/stylekit/main.py b/examples/quickcontrols/stylekit/main.py new file mode 100644 index 00000000..07286096 --- /dev/null +++ b/examples/quickcontrols/stylekit/main.py @@ -0,0 +1,29 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +""" +PySide6 port of Qt Quick Controls Style Kit example from Qt v6.x +""" +import sys +from pathlib import Path +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + +import rc_stylekit # noqa: F401 + + +if __name__ == '__main__': + app = QGuiApplication(sys.argv) + app.setOrganizationName("QtProject") + app.setApplicationName("StyleKit") + engine = QQmlApplicationEngine() + + engine.addImportPath(Path(__file__).parent) + engine.loadFromModule("StyleKitExampleModule", "Main") + + if not engine.rootObjects(): + sys.exit(-1) + + exit_code = app.exec() + del engine + sys.exit(exit_code) diff --git a/examples/quickcontrols/stylekit/rc_stylekit.py b/examples/quickcontrols/stylekit/rc_stylekit.py new file mode 100644 index 00000000..907a5727 --- /dev/null +++ b/examples/quickcontrols/stylekit/rc_stylekit.py @@ -0,0 +1,330 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.11.1 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x0b\x8a\ +\x00\ +\x0043x\x9c\xed[\xdb{\xd4\xc6\x15\x1f\x8c\xb1\x8d\ +\x81\xdcC\xae_\xa2@C\xb5d\xd9\xac\xd7W\x02&\ +\x80M\x03)\xc4$v\xc9\xc5!\x8b\xbc\xab\xb5\x15\xb4\ +\xd2F\xd2\xc2\xda\x84\x96\xa6IK\xd34}\xe9c\xff\ +\x89\xf6O\xe8s\x9f\xfbg\xf4\xb9/\xbd\x9c3\xf3\x9b\ +\xdd\x91VkL\xbe\xe6\xa1\xad\xc5'V\xe7w\xaes\ +\xe6\xcc\x9c\x91\xf8\x10B\xec\x17B\x0c\xd3\xbd\x87\xeeg\ +\xc5g\x22\x11U\xb1\x22\x5c\xd1\x11\x0b\x22\xa4?\x91\xa8\ +\x8b2\xf1\x86\xe8\x16\xff\xc2%r.6\xf0\x84h\x90\ +\x82#\xd6\xa5\xb2/\xd5\x95\xf9\x07\xaa\x8e\x885\xd1&\ +u!\xc6\xc8@D&bz\xbeoH\x1d\x90\x0eT\ +\x84WH&!)\x8f\xe2T\x1c\xbe\xce\x1a\xd2\x8f\xe9\ +\x87\xa7\xa0\xb2$Z\xa4T#\x95Dl\xc2\xe9Y\x1d\ +]\xf6z\x91\xbc\x87\x14OD\x0a\xae\xb8D*\xaeh\ +\x8aeR\xde\xa2'\x95\x8d\x8b2\xd6\x9c\xeb0\x0dE\ +%\xce\xa5\xbf\xb3\x99\xb8j\x06g^\xcf\xcb\x00#\xf2\ +\xe5\x91W\x9f\x94\xb3\xaa7\x06\xa9\x1eIy\xe4\xe4\xc4\ +\xe2\xa6\xb8 \x02zZ\x93\xa6\xea\x18qk\xd0\x88\x8f\ +\xe5\x9aX\x11\x1bD\xb9\xf4\xbc!c\xd1f\x92Af\ +\x9e\xed\x1bD/i\xac\xd8\x19\xa4\xf8R\x9fb\xde\x84\ +}\xbes\xf5E\xba\x03\x0a\xdcT\xbf7H}L*\ +6\xbba~9HP_,t0U$B\xd8\xa8\ +\xef=\xdb)\xee\xf4\x1a\xc1o\xbd\x07\x1d\xbc\xb7w\xe8\ +\xe8(\xdb?@\x01\xff\x0e\xe8\xe3T\x8c\xec\xf1\x00\xa9\ +\xf0\xef[\x97\x97/\x97\xe2\xa4^\x9a\x9a\xe6u+\x1e\ +\x11{\xbb\x11?*F\xe5\xb0\xf8n:^\xc0\xb8-\ +\xd42{\x8c\xe4\x18\x1fe\x1b\xf4\xf4*\xfd\xbe\x02=\ +M\x1f\x81O\xa6Y\xef\xa5.\xbdW\x94\x84^\x16\xc3\ +\xf2\x99\xaf}\x92\xde\xd7\xa5G\x85*`\x13;\x8a_\ +\x8d\xed\x01v\xd6\xc0\x86\x80]4\xb0\xbd\xc0\xae\x1a\xd8\ +0\xb0\x1b\x06\xb6\x0fX\xcb\xc0F\x80%\x066\x0a\xac\ +c`c\xc0>7\xb0\xfd\xc0\xee\x19\xd88\xb0/\x91\ +\x9b2r'\x0c\xda\xcc\xdd}#wO\xd2\xe8\x86\xa4\ +\xfc^9&~>LO\x1c\xa3E\xf73$?\x8a\ +\x98\x87$6,\xe3\x1a\xc5\xcd\xf4~<\x8fH;\xfb\ +d<#\x88\x9fy\xaf\x91\x0c\xd3O\xd3}\xf2\xafK\ +\xe74}\x98\xee\xcb\xb7\xffp\xbeH:\xa3\xd2\x97\x92\ +9l\xe8\x92\x19\xbb\xf7\xa6\x96\ +\xffX\xa8\xba\xe7\xf8\x1d\xc4/ \xcf\xf4\x1a\xeci\xf9\ +O3\xe3\xe3\xb52&T\xed\xddB,\xda\xffm\x8c\ +\xed\x96!\x7f\xc7\xa8\x89_A\xf6e\xe4\x93\xe9\xfb\xc0\ +f(\xaa!\xd1\xdbZ\x19\xfb;!\xfb \xcbq\xbe\ +b\xcc\xf7\x98\xf4\x91\xa6g\x0d\x9asv6\xc3?\x9f\ +\xe1/f\xe8\x8b\x19\xfa\x9d\x0c\xfda\x86\xbe\x91\xa1\xd7\ +3t`\xd0\x9c\x9bN\x86\xfe\x02\xf4<\xd1\x5c\xe7\xc7\ +\x84\xaa\xf1y\xac\xeb\x82P5\xfd>e\x81sv\x1c\ +2\x8c\x9f\xa1\x0c\xbd\x02L\xcb\x17!\x7f\x8e\xe4\xb9\xf6\ +\xa6\x84\xaa\xd7\x8a!3\x0d\xfc+\xac\xeb\x19\xe8M\xc3\ +\xe6k\xc0\xb4\xfc\x1c\xf2\xcc6\xdf\x90\xb1+\x9b'!\ +\xc3\xf3\x7f\x1a\xf8U\xd8\x9c\x07vZ\xce\xaf\xc2\xce\xc0\ +\x16\xf3\x0e\x92\x16co\xa2\xb6\xc6\xc0g\xff\xb3\xc0\xb5\ +\xff\x05\xcc+\xf3\xce\x83\xd6\xb1\x5c@,\xe7\x8cX~\ +\x04\x9c\xe5\x17A\x9f$\x0e\xf3\xde\xa2\xfb\x10\xea`\x11\ +2g\x81k\xfdK\xa8\x1b\xed\xe3\xc7\xf0\xf1\xb6!s\ +\x19\xf8A\x8a\x90\xe9+\x18G\x19\xfa\x97a\xfb\x22x\ +\xda\xd6U\xc8,\x19\xb6\xde\x05>\x8f=\xf4=\xa1\xea\ +\x9cux=\xff\x04:\xcb\x86\xcc5\xe0\xda\xff\xfb\xf0\ +\xbf\x00\xfdk\x06\xef\x03#\xb6w!\xcb\xb1\xbd\x03\x9e\ +\x8ecU\xa8z\xd7\xb1^\x87\xce\xc7\x86\xcc'\xc0\x0f\ +\xd2\x8c1]\x85m\xde\x1f?\x82\x8dO`\xffC\xf0\ +\xd9\x1e\xaf\x85\x1a\xc6\xb5f\xd8\xab\x03\xd74\x9f\x94X\ +\x8fk\x93\xe9\x06d\x5c\xd8\xbc\x01L\xdb\xdc\xc8\xb1\xe9\ +\x01\xd7\xe3\xb8\x89q|j\xc8\xf8\xc0\xb5\x9f&\xf4|\ +\xf8Y\x07\xa6\xe5C\xa1\xf6\x06m\xf33\xd8l\x192\ +\x11pm3\x86^d\xc8\xf0\x99\xe2\x86!\xd3\x86\x5c\ +\x02\xbf\x010=\xcf\x9b\x18\xdf\x12\xe5\x9b\xf7\xd4-`\ +|\x0b\xe4~\xc8\xa8\x95\xcf\x11\xd7\x1d\xc3\xc6]\xe0\xda\ +\xc6O\x81\xdd\xcd\xd8\xd01\xfeL\xa8}\xeb*Q,\ +\x7f\x0f\x98\xbe\x0f\xc2\xce\xcf\x8d\xb9\xdf\x82\xdd{\x18G\ +\x07\xfcy\xf4\x88_\x08\xb5\xf7\xe9\xfc}\x993'_\ +\x01\xff\x96dX\xe7\x97\xd0\xfb\x0a6\xbf\x00\xa6m\xfe\ +Z\xa8\xfdS\xeb\x7f-\xd4\xfe\xfb.r\xfb\x1b\xc8\x08\ +\x03\xfb\x06\xd8\x1e\x03\xfb-\xb0!9\xe6Q\x99\xb3o\ +\xa1\xff\x0d\xf8_#\x86\xfb\xe0\xfd\x83,\xcc\xd1=\x03\ +\x1b\x87\xe0\x87\xfb\xf6,\xc9\xf1~v\x00\xcf\xfbe\xce\ +T/{D\xf4z\xd1\xa3\xa2\xb7\xf7\xeb\xb3\xef\x01c\ +<\x8fCO\xef\xa9O\x00{\x5c\xe6VaOB\xef\ +\x09\xc4\xf7(0m\xf3)\xf8\xf9=\xe2|\x16\xd83\ +B\xed\xc1\x8c=\x87|\x1c\x02_\xd7\xe6\x0b\xe0=o\ +\xc8\xbe\x08\xd9q\xf0\xffIcblN\x9d\xe2\x86\xf1\ +\xde@\xc4\x9e\xb3\xa2{\xb4\x1bY>z\xcb\x8db/\ +\x0c\xac\xc9J\xd9r\xe3\xf1V\xe4\xd6<\x09lx\xeb\ +\x1b-\xab\xe1\x87Nr\xaa\x0f\xf6\x02\x02\xc7\xe3$j\ +\xd7\x12k\xad\xdd\x18\xbf3n\xd1\xd5t\x92)\xeb\xb3\ +\xa4z\xc5I\x22\xafsJb\xd2\x02\x83K-\xa7\xe6\ +%\x9b\x0a\xbd\xe5\xd6*V\x1c\xb6\xa3\x9a{)q\x9b\ +\xcb\xde\x96\xdbeLYkaTw\xa3\x85\xd0\x0f#\ +\x03m9Q\xe2\xd5|\xd7\xc0\x95q%~\xc5\x89o\ +^\x08\x9c5\xdf\xad\xe73W6\x227\xde\x08\xfd\x14\ +[\x1b\xedE\x90\xc6SQ\xa7Y\x8bn\x10gX\x89\ +\xd7$+w)7\xed\xc0k\x84Q\x93\x93c9\xd1\ +zL\x90\xefl\x86\xed\xc4^\xf3\x82\xba\x17\xac[\xf3\ +\xd6D\xc1\xd2b*\xad\xb1\xd3l\xf9nTYDj\ +zJ~Xs\x12\xce\xff\xbcU.P\xfaU\x02)\ +\xab+ng!\xa4!\x96O\xe5\x8b\x12\xa2\xb2\xd7\x88\ +\x9cudn\x5c\x05\x1b9A=l\xdaj*\x92\xa2\ +\x1eB\x01\x93)\xf1\xb0\xd1\x88\xddd9!cqb\ +\xbd&A;)\xa81Gn\xd2\x8e\x02\xb6\x5cK\xec\ +\xd8\x0b\xecz\x98\xd8Z\xa3\xa8d'*\xa5\x93s'\ +\xe7\xca\xe5\xa9\xe9\xc9\x89\xb9\x99\xf2\xe4\xf4\xc4\xf4Le\ +\xbah\xcd\xce\x95*\x93\x93\xe5\xf2\xc4l\x99\xf8S\x93\ +\xb3\xd3\x85B\xc1:n\xf1\xd3\x5cizjf\x8e\x10\ +\xca\xe5\xf8\xf8\xad\xd0\xab[\x5c\xc0\xb6\x11\xda\x14R$\ +\x87D\xd1%n\x87\x82qm\x85\x16S\xa9)\x185\ +\xd7\xf2:\xae/aR2\x85\xc85\xcfSi@Q\ +V\xac \xf4bWkR\xae\xc2\xc86\x8c\xbd\xae\x86\ ++M\x985UH9w\x22\xa7I\xda=S\x99\xba\ +r\x9a\xd5\x09\xe2K+\xaa\x96zl5]\xd7\x1c\xbf\ +\xed\x92\x08&O\xea\x14\xb5j\xc1\x94\x97N\xaex\x1d\ +9un\xcb6\xf4\x8bV*N\x14rJ\xbb\xe6\xf8\ +\xb5\xb6\xef$n\xfd|w\x01iKR9g]\x15\ +\xad\xba\x17'NP\xd3\xd3 '\x07\xce\x8c5](\ +\xe4/P\xb2\xdf\xf4:\xf6D\xa9\x5c\xcc\xf5\x9f\xb2d\ +,\xf7\x94\xb9\x86\x178\xbe\x8e\xb6\x17E\xe96Mp\ +O\xb3O\xe3\x9c\xdf\xdaprT\xa4\xc7\xec\xee\xa5\xf4\ +\x9ad\xc7\xad\xbf\xc3i\xd6\xdav7\xe9\xc7\xd3)\x86\ +:\x17x7\xc0naLZ4\x8a\xa0\xee\xd6u1\ +s\x16\xcc@:\x9b[\x99)3p6`gc1\ +\xcan\xd2\xa2\xdd\xfbJ\xdbO\xbc\x96\xef\xf5|\xa4\x5c\ +\x0e\x1a\xa7\xde3H\x9e\xd7\x9c\xddo\xa9h\xa4O-\ +\xd7L\xbb\xf9\xb3\xe8^#/u\xdb\xcd\xd4ty\xb7\ +\x7f\xfc\x07\xfb\xc7n\xe7\xd8\xed\x1c\xbb\x9dc\xb7s\xfc\ +/u\x8e\xbf\xe4w\x8e\x99\xdd\xce\xb1\xdb9v;\xc7\ +n\xe7\xd8\xed\x1c\xbb\x9d\xa3\xbfs\xf0\xd7\xc2J\xafs\ +\x8c\xfd\xb1F\x1ba\xc3\x8d\xe4~\xf8\x06\xed\x1f\xebT\ +/nd\xaf\x95\xf5\xca\x8e\xc2\xdb\xd5\xa6\xf3)y\x94\ +9\x9e\xeaL\xc9\xc8\xaa\xddvBj\x14\xe1M\xb5\xcb\ +\xd8\xb5rj\xee\xb5(\x06\x91\x91\x9d2e+J8\ +\xbd\xda\xb3\x0a\xa5-S\x05\xa1\x18\x85\x9c\x91\x9f\xce\x91\ +NMXF~\xa6?\xf8\xbe\xda\xce\xa8\xccn\xa7\xd2\ +]\x88Y\xa5\xd2f\xbf\x9a\xb9I\xf5\xc9o\x0d\x96\xcf\ +\xcf\xedl\xe9\xf6`\x15\xec/\x19\x95\xb9~\x05\xde\xf2\ +\xb2R2v\xee\xa5+j\x87\xaf,\x9eV\xf9=\x83\ +\xb5j\x16R\xc2\xf5\xb0\xac\xba\xe8rB\xbb\x87\x85\x19\ +\xae\xa2\xb5\x9a\xc21\x0b\xd3\xf1\x85\xbaeM\x17E\xba\ +\xa3\x9a\xbct\x07\xc5\xa1g\xf9\xea\xa5\xf7\xaeU\x17\xa2\ +0\x8e\xab\x97\x82V;A\x19\xe7X#\xcf+\x17>\ +XXZzo\xb1\xac\x06\x94cc\xa9\x9dd\x8c\x18\ +n\xc9\xc2\xf2\xb5\xea\x0ae\xcaM`\x22\xd5\xc8\xe14\ +\xa7\x95\x83\x93m\xe6I\xa9\xd3\xe9\xeb\xe49\x8d\x5c\xa9\ +\x0fj\xe5\x8d\xbc^\xde\xe8o\xe6\x0d\xa3\x9b\xf3\x98\xaa\ +\xa9\x96\x8e\xc1\xa6\x9b\xba\xa2JjB\xed\xccT\xe6\xf6\ +x\x8c\xf4A]\xbe\x9a\xd7\xe5\xa1\xfb\xa0>\xdf\xb7z\ +(\x8b\x19\xef\x0f\xd1\xe6\xab\xdf{\x9b\xcf.\xc3\x87j\ +\xf3y\xbb\xcbvm\xbe\xba\xb36O\xd3\xd7\xe2>\xdf\ +\xd8\xae\xd1\xf7o\x86\xdfw\xa3\xaf~\xe7F\x9f\xdd\x1e\ +s\x1a\xbd\xb4\xd5\xd7\xeae&r{}5\xa7\xd7g\ +#\xa1\xd2K\xd5\xde\xce{}\xffP\x8d^\xaf\x16\xe3\ +\x8e\xba}\xff\xee\xa5\x8e\xea};#\xd5\x94\xb3\xeeV\ +=~\xd6\x8b>\xb52\xe7M\x89Rz#\xd6\x01b\ +\xcfP@\x8eke!\x94\x84\x122\x91Rj\x8c\xbd\ +\xdd\xdc\xd8\x01\xd3\x06\xee\x9a\x87\x99a\xfc\xb3 \xae\xd1\ +?\x1dm\x91\x89\xa6c\xd5|\x87^\xe4\xea\x9e\xb3\x1e\ +\x841\xf7\x0b\x8f\x1e\x22j\xddGN\xbc\xdf\xf4b\xda\ +O\xd7O\xb4\xa20\x09\x93\xcd\x96\x1b\x1f\x19\x1f?\xea\ +\x055\xbf]w\xad\xd3M7q\xfcj\x9c\xd4}o\ +\xed\x8c\x81\xc7^\xb3\xfe:\xffU\xda8Co\x91l\ +\xc2\x0a\x9c\xa6\x1b\xd3\x8c\xb9\x96\xd4\xca\xfb7\xbf\xee\x99\ +iGo\xdf\xba_\x0c\xda\x0as\xde\xc0\x81\xffW\xbd\ +\x83#K<\x8be\x9e\xd9A\xdduu\xb5\xc6\xbfv\ +\xb9p\xfdz\x8e\xa6\x17l\xd3\xdbWW\xdb1\x9d(\ +\xe8\xd5;0\xd5\xe5\xe9\xc1\x0b|/\xa0\x83H\xd5I\ +hF\xd6\xda\x89[\xad\xda\xb6\xe3\xdfv6\xe3\xaab\ +\x16\x0a\xe9N\x9eP~\x9c\xbaU\x0b\x03\xea\xd3\xca\xdf\ +1\xd9\xd7\xfb\x19\xc7\x1e\xd8\xe5\xd1\xbdw\xf8\xd2\xbe}\ +\xaf\x7f\x98\xd7v\xcel\xd3\x0d\x8c\xcc\xab'[\xe7\x93\ +?c\xac\xae\xeaE\x7f\xfdzQ\x8d\xcb\x09dI\x1f\ +\x93\xdb\x14\x09\xa8W\x069-E\xfd\x9a_\xa9\xabC\ +`\xf7\x0c\xb8\xba\xaa?\x00(9}\xdcS\xece\x22\ +x\x82\x81J\x99B\xf7k\x95\x8e\x8e\xefy\xeb\xce\xdd\ +T\xa9\xe7\x1eH\x94\x1d\xdb0^\xa4\xc1\x94vx\x1e\ +\xc9Hn\xf7\xe1a\x87G\x12L\xda\xc0\x8f\x0f\xdf\xe1\ +\x5c\xb2\xfb\xf9\xe1\xff\xe3\xf3C\xfe\xa9\xe4\xa1>@(\ +\x13\x03?A<\xe4\xc1\xa4o\xb4a\xb6q\xef\xf0p\ +blv\xa1j\xe6\xdc\xcd\xf7\xc9\xf5\x8eo\x13\x99\xef\ +\x13\xea\xbf\xe8\xf0\x95\xf3\xff\x102\xed\x7f[Y\x01:\ +\xab#\xaf\x7f\x03-\xdb\xc3\x08\ +\x00\x00\x06\x17\ +\x00\ +\x00\x15\x0dx\x9c\xedXmO\xdbV\x14\xbe\x84@\xda\ +\x94\xbe\xd3\xb7u+\xa6l\x9d\xd3R7\xa5)\xebJ\ +AL0\xadHTT\x05u\x93\x22d\x19\xe7&\xf5\ +\x96\xd8\xa9}\xdd\x86Uh\xda\x1f\xe8\x8f\xda\xe7\xfd\x83\ +I\xd3~\xc6\xbeL\xdb9\xf7\x9e\xeb\xdc8\x81U\x93\ +\xf6aS\x8d\x9c\xf8<\xf79\xaf\xf7\x1c_\x801v\ +\x9c1V\x84{\x0c\xee\xcb\xec%\x13\xcce;\x8c\xb3\ +\x1e[c\x11\xfc\xc4\xac\xc1\xaa\xb0V\x80\x9b\xfdI\x17\ +\x1bq\xa1\x81s\xac\x09\x0a\x1ekI\xe5\xb6TW\xe6\ +\xffVu\x92\xed\xb1\x14\xd4\x19;\x06\x06b0\x91\xc0\ +\xf3S\x835!\x1d\xa8\x08\x9f\x00G\x00+\x808\x19\ +;A\x8cU\x83}F?L\x93\xca\x16\xeb\x82\x92\x0f\ +*\x82\xed\x93\xd3U\x1d]\xfe:\x07\xd4\x0e(\xb4%\ +=\x85\x1apRY?\x5c\x05s\xe7\xe0-\x85\xcf\x10\ +\xa0z\xcd\x10\xf6\x07T\xf2\x81\xea\xd6\x22\xbd\xfb\ +@\x18[eY\x0b\x17\xce\xcf\xbd\xe2q\x12D\xa1u\ +o\xa1j\xf1\xa4\xdc\x8d\xb9\x1fH\xe0E\xd0z\xd1\xb5\ +\x9a\xed\xc8\x13KCp\x10\x02XND\x9c\xfa\xc2\xda\ +K\x9b\xe57e\x0b\xae\x8e'j\xd6K\xe1>\xf1D\ +\x1c\xf4\x96$&- \xb8\xd5\xf5\xfc@\xec\x9b\xa8\xd7\ +\xe9\xb6\x03\x916\xb8\x096c\xfe2\xe5\xa1?\xc0\x14\ +A\x07H\x07\xe04\x0d\x83f\x14w\xd0\xab\xe5\xc5\xad\ +\x04\xa0\xb6\xb7\x1f\xa5\xc2\xde\x0b\xc2F\x10\xb6\xace\xeb\ +n\xc5\xd24\x15o\x82\x9ex\xbc\xb0n%Q\x1a\xfb\ +|C\xf0N_\xb1\x1d\xf9\x9e\xc0\xe4\x96\xadj\x05r\ +\xb3^q\x7f\x01C\xde\xe1\xbd\xb5(\x8a\x1b\xd5\xa5\xd1\ +T@\x90[\x83\x98\xbd\xd6Z\xd4\x8eb0\xfa*\x0a\ +\x1a\x16\xd6\xdb\xaePY\xa4\xb9.\xa8$\x00\xa2`c\ +\xe0\x0e\xe6T\xb1nY\xb6\xe9\xc9\xba)\xb3r\xb2*\ +T*T\x07\xed\x02\xec\x08\xde\x13i\xcc\xed~2\xf3\ +\x03\xe1\xa2Q\xe9\xa7\xeb\xec\xcf[\xb7\xbbN\xaf\xa2\xed\ +f%\xafd\x90\xb97\x07\xe5r\xae]~b\xd95\ +\xf6k\xd6.p\xd4\xfe7\xf6\xff\xfd\xce\xff\xe3\x9d\xff\ +y\xf4\xce/\xbe\xdf\xf9\xff\xf3\xce\x17\xe8\xb4\xa2k\xfc\ +\x17\x1f\xea\xdd\xe4\xb1,\xfbC+\xe6\xad \x11<\xb6\ +\xf7\xaa:\xd58z\xedv\xbco!J\xb9c\xb5^\ +M\xfap\xb3v\x005\xf0\xf5]\xd4l&\x5c\xd8~\ +\xb52\xd0\x09D\xa5pr\xdc\xda07\xcb&Ou\ +\xf6\x87\xc9YI\x87\xc8\xdf\x0f\x93qs\x86x\xaf+\ +\xaa\xfbvT\xfd\x17\xd6\x1f\xa9,W\x8c\xb62\xeb\x22\ +0\xbdm\xd5{\xdb\xc2\x13\xdcr\xfbD\x97\x9a\xd2T\ +HP\x01&\x0az\xccW\xd1\xe4\xfb\xd0\x5c\x1b\xec;\ +\x9a\xc3\xed\xa7\x1b\xcf\x9e\xbbkq\x94$\xeeF\xd8M\ +\x05\xed\xcc\x08k\xe0y\xe7\xcbo\xd6\xb6\xb6\x9e\xadW\ +Ub#ll\xa5\x22g\xc4p\x0b\x16\xb6\x9f\xbb;\ +P1.\xc8\x84l\x7f$\xb8\x033@\xee\xf5\x14d\ +5vz\xbdCF\xc0=r\x04\xfautT\x81\xed\ +\x11\xa5\x1d\x9e\x0a\x15\xc6\xf0\x5c\xb8\xc3s\xe1\xe6\xe6b\ +\xb8(j\xca\x87\x0an\xc1\x1e\xb5\xb8\x1b\xe0\xb3N\x7f\ + \x8ee\x93\xe1\x0c\xee\xaf\xce\x93\xaa\xa7\x80\x11\xae\x95\ +\x85H\x0a\x8ad\x22\x8eY*\xa3I\xe4\x88r\xe8\xdd\ +0g\xe0\xc0\x1c\xfb\x22\xfd\x92OW\xe1\xb7\xb9 \xf4\ +\xdb8e\x8f:\x5cxm\x17\xfe\xc0n\x07{+\xe5\ +>\x9e\x04\x9d\xc6\x1d\xfcp^\xac\xc0\xdb9\xc17p\ +\xe8ux\x02\x05\xe4\x96\xd4\x1a\xf5Kb\xf6\x92\xf8\x97\ +\x8f\x0br\x8c\xf9U1\xe7\xc3\xda\xb9^\xf7\xf1\xdb\xae\ +VvwGh\x06\xe1\x11\xc3T\xaf\xa7\x09\x8c0\x9c\ +\x10a_\x1dMwxh\xb8VO\xb66\x88\xc7M\ +\xbd\xae\xfbaww\xde\xf2\xa3\x10\xc4P\x96\xe9\x86\xec\ +D \xa8\xf7\xae\x8ck^\xbf\xfe\x17\x1a\xea\xf53\xf0\ +\xf6\xa9\xd7\xf5\xe1\xa0\xb8\xfa%\xd3\xa7l\x03\x80\x99\xd2\ +\x8a\xe4U\xb2\xa3[G\x89\xf7\xb2\xf5\xe6`i\xd4\xf4\ +\xd2\x14\x0d\x9ebA\xe8\xbc\xd3A\x16\xe5\xda\xd3\x98d\ +\x15\x92\x9d\x8bu~\xd8\xf6\x11\x93l\x9ep#\x8f8\ +c\x06\x22\xd5\xfa\xd8\xfb\x132u:\xf3r\xe7\x9e\xfa\ +\xe7#^#\xfe\xc7\x95\x1b\x96#\xb9\x8c\xe4\xbc\x8e\xbc\ +\xfe\x02/\x00\xa1a\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x0b\xcc\xbas\ +\x00e\ +\x00f\x00f\x00e\x00c\x00t\x00s\ +\x00\x09\ +\x00\x98\xb2R\ +\x00n\ +\x00o\x00i\x00s\x00e\x00.\x00q\x00s\x00b\ +\x00\x08\ +\x08\xc8W2\ +\x00w\ +\x00a\x00v\x00e\x00.\x00q\x00s\x00b\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9dg\x02\xe4\x14\ +\x00\x00\x00,\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x8e\ +\x00\x00\x01\x9dg\x02\xe4\x14\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/quickcontrols/stylekit/shaders/noise.frag b/examples/quickcontrols/stylekit/shaders/noise.frag new file mode 100644 index 00000000..019ec500 --- /dev/null +++ b/examples/quickcontrols/stylekit/shaders/noise.frag @@ -0,0 +1,48 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#version 440 +precision highp float; + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D source; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; // reserved + float qt_Opacity; // reserved + vec2 sourceItemSize; // The size of the input item. The source is divided into a grid of cells. + vec4 borderColor; // The color of the border (to be masked away). Set to transparent to ignore. + vec4 particleColor; // The color of the noise particle + float borderMaskEnabled; // Enabled mask or not. If the bg and border color is the same, set this to 0 + float borderMaskThreshold; // The threshold for determining if a pixel belongs to the border (taking anti-aliasing into account) + float particleSize; // The size of a dust particle (aka the cell size in the grid) + float particleOpacity; // The particleOpacity of the particle + float particleDensity; // The threshold deciding if a particle (aka cell in the grid) should be visible or not + float time; // time, for animating the noise +} args; + +float random(vec2 st, float t) { + vec2 offsetSt = st + t; + return fract(sin(dot(offsetSt.xy, vec2(12.9898, 78.233))) * 43758.5453123); +} + +void main() { + vec4 sourceColor = texture(source, qt_TexCoord0); + vec2 pixelCoord = qt_TexCoord0 * args.sourceItemSize; + vec2 noiseCoord = floor(pixelCoord / args.particleSize); + float randomValue = random(noiseCoord, args.time); + float noiseMix = step(randomValue, args.particleDensity); + + float calculatedBorderMask = step(args.borderMaskThreshold, distance(sourceColor.rgba, args.borderColor.rgba)); + float borderMask = mix(1.0, calculatedBorderMask, args.borderMaskEnabled); + float finalMask = sourceColor.a * borderMask; + float finalAlpha = sourceColor.a * args.qt_Opacity; + float maskedNoiseAlpha = noiseMix * args.particleOpacity * finalMask; + + vec3 blendedColor = mix(sourceColor.rgb, args.particleColor.rgb, maskedNoiseAlpha); + vec3 preMultipliedColor = blendedColor * args.qt_Opacity; + + fragColor = vec4(preMultipliedColor, finalAlpha); +} diff --git a/examples/quickcontrols/stylekit/shaders/prebuilt/noise.qsb b/examples/quickcontrols/stylekit/shaders/prebuilt/noise.qsb new file mode 100644 index 00000000..4752d1bf Binary files /dev/null and b/examples/quickcontrols/stylekit/shaders/prebuilt/noise.qsb differ diff --git a/examples/quickcontrols/stylekit/shaders/prebuilt/wave.qsb b/examples/quickcontrols/stylekit/shaders/prebuilt/wave.qsb new file mode 100644 index 00000000..8e7a8678 Binary files /dev/null and b/examples/quickcontrols/stylekit/shaders/prebuilt/wave.qsb differ diff --git a/examples/quickcontrols/stylekit/shaders/wave.frag b/examples/quickcontrols/stylekit/shaders/wave.frag new file mode 100644 index 00000000..8338d6e9 --- /dev/null +++ b/examples/quickcontrols/stylekit/shaders/wave.frag @@ -0,0 +1,23 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#version 440 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(binding = 1) uniform sampler2D sourceItem; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; // reserved + float qt_Opacity; // reserved + float amplitude; + float frequency; + float time; +} args; + +void main() +{ + vec2 p = sin(args.time + args.frequency * qt_TexCoord0); + fragColor = texture(sourceItem, qt_TexCoord0 + args.amplitude * vec2(p.y, -p.x)) * args.qt_Opacity; +} diff --git a/examples/quickcontrols/stylekit/stylekit.pyproject b/examples/quickcontrols/stylekit/stylekit.pyproject new file mode 100644 index 00000000..83004ae8 --- /dev/null +++ b/examples/quickcontrols/stylekit/stylekit.pyproject @@ -0,0 +1,10 @@ +{ + "files": ["main.py", + "shaders/noise.frag", "shaders/wave.frag", + "stylekit.qrc", + "StyleKitExampleModule/Main.qml", + "StyleKitExampleModule/styles/CustomDelegates.qml", + "StyleKitExampleModule/styles/Haze.qml", + "StyleKitExampleModule/styles/Plain.qml", + "StyleKitExampleModule/styles/Vitrum.qml"] +} diff --git a/examples/quickcontrols/stylekit/stylekit.qrc b/examples/quickcontrols/stylekit/stylekit.qrc new file mode 100644 index 00000000..95d783e0 --- /dev/null +++ b/examples/quickcontrols/stylekit/stylekit.qrc @@ -0,0 +1,6 @@ + + + shaders/prebuilt/noise.qsb + shaders/prebuilt/wave.qsb + + diff --git a/examples/samplebinding/CMakeLists.txt b/examples/samplebinding/CMakeLists.txt index 3f355d36..f985a42b 100644 --- a/examples/samplebinding/CMakeLists.txt +++ b/examples/samplebinding/CMakeLists.txt @@ -1,8 +1,8 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.18) -cmake_policy(VERSION 3.18) +cmake_minimum_required(VERSION 3.22) +cmake_policy(VERSION 3.22) # Enable policy to not use RPATH settings for install_name on macOS. if(POLICY CMP0068) diff --git a/examples/scriptableapplication/CMakeLists.txt b/examples/scriptableapplication/CMakeLists.txt index 18bb17c4..b63fb4fa 100644 --- a/examples/scriptableapplication/CMakeLists.txt +++ b/examples/scriptableapplication/CMakeLists.txt @@ -1,8 +1,8 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.18) -cmake_policy(VERSION 3.18) +cmake_minimum_required(VERSION 3.22) +cmake_policy(VERSION 3.22) # Enable policy to run automoc on generated files. if(POLICY CMP0071) diff --git a/examples/scriptableapplication/doc/scriptableapplication.rst b/examples/scriptableapplication/doc/scriptableapplication.rst index bbabb124..ddbea200 100644 --- a/examples/scriptableapplication/doc/scriptableapplication.rst +++ b/examples/scriptableapplication/doc/scriptableapplication.rst @@ -25,18 +25,16 @@ which trigger the underlying C++ function. Building the project ******************** -This example can be built using ``CMake`` or ``QMake``, +This example can be built using ``CMake``, but there are common requirements that you need to take into consideration: * Make sure that a --standalone PySide package (bundled with Qt libraries) is installed into the current active Python environment (system or virtualenv) -* qmake has to be in your PATH: * so that CMake find_package(Qt6 COMPONENTS Core) works (used for include headers), - * used for building the application with qmake instead of CMake * use the same Qt version for building the example application, as was used for building PySide, this is to ensure binary compatibility between the @@ -55,8 +53,7 @@ For Windows you will also need: compatible version of MSVC, to avoid mixing of C++ runtime libraries. Both build options will use the ``pyside_config.py`` file to configure the project -using the current PySide/Shiboken installation (for qmake via ``pyside.pri``, -and for CMake via the project ``CMakeLists.txt``). +using the current PySide/Shiboken installation (for CMake via ``CMakeLists.txt``). Using CMake @@ -89,33 +86,9 @@ On Windows: ninja .\scriptableapplication.exe -Using QMake -+++++++++++ - -The file ``scriptableapplication.pro`` is the project file associated -to the example when using qmake. - -You can build this example by executing: - -.. code-block:: bash - - mkdir build - cd build - qmake .. - make # or nmake / jom for Windows - - Windows troubleshooting *********************** -Using ``qmake`` should work out of the box, there was a known issue -with directories and white spaces that is solved by using the -"~1" character, so the path will change from: -``c:\Program Files\Python39\libs`` -to -``c:\Progra~1\Python39\libs`` -this will avoid the issues when the Makefiles are generated. - It is possible when using ``CMake`` to pick up the wrong compiler for a different architecture, but it can be addressed explicitly by setting the ``CC`` environment variable: @@ -182,11 +155,11 @@ In practice this means the only supported configurations are: #. release config build of the application + PySide ``setup.py`` without ``--debug`` flag + ``python.exe`` for the - PySide build process + ``python39.dll`` for the linked in shared + PySide build process + ``python314.dll`` for the linked in shared library + release build of Qt. #. debug config build of the application + PySide ``setup.py`` *with* ``--debug`` flag + ``python_d.exe`` for the - PySide build process + ``python39_d.dll`` for the linked in shared + PySide build process + ``python314_d.dll`` for the linked in shared library + debug build of Qt. This is necessary because all the shared libraries in question have to diff --git a/examples/scriptableapplication/pythonutils.cpp b/examples/scriptableapplication/pythonutils.cpp index f726353f..08943045 100644 --- a/examples/scriptableapplication/pythonutils.cpp +++ b/examples/scriptableapplication/pythonutils.cpp @@ -21,10 +21,7 @@ extern "C" PyObject *PyInit_AppLib(); static const char moduleName[] = "AppLib"; // This variable stores all Python types exported by this module. -extern PyTypeObject **SbkAppLibTypes; - -// This variable stores all type converters exported by this module. -extern SbkConverter **SbkAppLibTypeConverters; +extern Shiboken::Module::TypeInitStruct *SbkAppLibTypeStructs; namespace PythonUtils { @@ -87,7 +84,7 @@ bool bindAppObject(const QString &moduleName, const QString &name, { if (init() != AppModuleLoaded) return false; - PyTypeObject *typeObject = SbkAppLibTypes[index]; + PyTypeObject *typeObject = SbkAppLibTypeStructs[index].type; PyObject *po = Shiboken::Conversions::pointerToPython(typeObject, o); if (!po) { diff --git a/examples/serialbus/can/mainwindow.py b/examples/serialbus/can/mainwindow.py index 5800af54..9a8aa3b4 100644 --- a/examples/serialbus/can/mainwindow.py +++ b/examples/serialbus/can/mainwindow.py @@ -25,7 +25,7 @@ def frame_flags(frame): def show_help(): - url = "http://doc.qt.io/qt-6/qtcanbus-backends.html#can-bus-plugins" + url = "https://doc.qt.io/qt-6/qtcanbus-backends.html#can-bus-plugins" QDesktopServices.openUrl(QUrl(url)) diff --git a/examples/serialbus/can/mainwindow.ui b/examples/serialbus/can/mainwindow.ui index 8c7ef841..e2eb1d97 100644 --- a/examples/serialbus/can/mainwindow.ui +++ b/examples/serialbus/can/mainwindow.ui @@ -11,7 +11,7 @@ - CAN Example + CAN Manager @@ -25,7 +25,7 @@ - Received CAN messages + Received CAN frames diff --git a/examples/serialbus/can/ui_mainwindow.py b/examples/serialbus/can/ui_mainwindow.py index e08c093a..a91ed99f 100644 --- a/examples/serialbus/can/ui_mainwindow.py +++ b/examples/serialbus/can/ui_mainwindow.py @@ -148,7 +148,7 @@ class Ui_MainWindow(object): # setupUi def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"CAN Example", None)) + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"CAN Manager", None)) self.actionConnect.setText(QCoreApplication.translate("MainWindow", u"&Connect", None)) self.actionDisconnect.setText(QCoreApplication.translate("MainWindow", u"&Disconnect", None)) self.actionQuit.setText(QCoreApplication.translate("MainWindow", u"&Quit", None)) @@ -164,7 +164,7 @@ class Ui_MainWindow(object): #endif // QT_CONFIG(tooltip) self.actionDeviceInformation.setText(QCoreApplication.translate("MainWindow", u"Device &Information...", None)) self.sendFrameBox.setTitle(QCoreApplication.translate("MainWindow", u"Send CAN frame", None)) - self.receivedMessagesBox.setTitle(QCoreApplication.translate("MainWindow", u"Received CAN messages", None)) + self.receivedMessagesBox.setTitle(QCoreApplication.translate("MainWindow", u"Received CAN frames", None)) self.busStatus.setText("") self.menuCalls.setTitle(QCoreApplication.translate("MainWindow", u"&Calls", None)) self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"&Help", None)) diff --git a/examples/serialbus/modbus/modbusclient/mainwindow.ui b/examples/serialbus/modbus/modbusclient/mainwindow.ui index 0681b99e..e72d302a 100644 --- a/examples/serialbus/modbus/modbusclient/mainwindow.ui +++ b/examples/serialbus/modbus/modbusclient/mainwindow.ui @@ -17,7 +17,7 @@ - Modbus Client Example + Modbus Client diff --git a/examples/serialbus/modbus/modbusclient/ui_mainwindow.py b/examples/serialbus/modbus/modbusclient/ui_mainwindow.py index a23e5fc6..915f6adf 100644 --- a/examples/serialbus/modbus/modbusclient/ui_mainwindow.py +++ b/examples/serialbus/modbus/modbusclient/ui_mainwindow.py @@ -325,7 +325,7 @@ class Ui_MainWindow(object): # setupUi def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Modbus Client Example", None)) + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Modbus Client", None)) self.actionConnect.setText(QCoreApplication.translate("MainWindow", u"&Connect", None)) self.actionDisconnect.setText(QCoreApplication.translate("MainWindow", u"&Disconnect", None)) self.actionExit.setText(QCoreApplication.translate("MainWindow", u"&Quit", None)) diff --git a/examples/speech/hello_speak/mainwindow.py b/examples/speech/hello_speak/mainwindow.py index cb46b55f..dc524264 100644 --- a/examples/speech/hello_speak/mainwindow.py +++ b/examples/speech/hello_speak/mainwindow.py @@ -49,14 +49,15 @@ class MainWindow(QMainWindow): @Slot(QTextToSpeech.State) def state_changed(self, state): - if state == QTextToSpeech.Speaking: - self._ui.statusbar.showMessage("Speech started...") - elif state == QTextToSpeech.Ready: - self._ui.statusbar.showMessage("Speech stopped...", 2000) - elif state == QTextToSpeech.Paused: - self._ui.statusbar.showMessage("Speech paused...") - else: - self._ui.statusbar.showMessage("Speech error!") + match state: + case QTextToSpeech.Speaking: + self._ui.statusbar.showMessage("Speech started...") + case QTextToSpeech.Ready: + self._ui.statusbar.showMessage("Speech stopped...", 2000) + case QTextToSpeech.Paused: + self._ui.statusbar.showMessage("Speech paused...") + case _: + self._ui.statusbar.showMessage("Speech error!") self._ui.pauseButton.setEnabled(state == QTextToSpeech.Speaking) self._ui.resumeButton.setEnabled(state == QTextToSpeech.Paused) diff --git a/examples/statemachine/rogue/rogue.py b/examples/statemachine/rogue/rogue.py index 20376b3c..18359011 100644 --- a/examples/statemachine/rogue/rogue.py +++ b/examples/statemachine/rogue/rogue.py @@ -145,18 +145,19 @@ class MainWindow(QMainWindow): painter.drawText(QPoint(self.pX * font_width, (self.pY + 2) * font_height), '@') def move_player(self, direction): - if direction == self.left: - if self.map[self.pX - 1][self.pY] != '#': - self.pX -= 1 - elif direction == self.right: - if self.map[self.pX + 1][self.pY] != '#': - self.pX += 1 - elif direction == self.Up: - if self.map[self.pX][self.pY - 1] != '#': - self.pY -= 1 - elif direction == self.down: - if self.map[self.pX][self.pY + 1] != '#': - self.pY += 1 + match direction: + case self.left: + if self.map[self.pX - 1][self.pY] != '#': + self.pX -= 1 + case self.right: + if self.map[self.pX + 1][self.pY] != '#': + self.pX += 1 + case self.Up: + if self.map[self.pX][self.pY - 1] != '#': + self.pY -= 1 + case self.down: + if self.map[self.pX][self.pY + 1] != '#': + self.pY += 1 self.repaint() def get_status(self): diff --git a/examples/tutorials/drumpad/final_project/qtquickcontrols2.conf b/examples/tutorials/drumpad/final_project/qtquickcontrols2.conf index 87a95d01..6c94a509 100644 --- a/examples/tutorials/drumpad/final_project/qtquickcontrols2.conf +++ b/examples/tutorials/drumpad/final_project/qtquickcontrols2.conf @@ -1,6 +1,6 @@ ; This file can be edited to change the style of the application ; Read "Qt Quick Controls 2 Configuration File" for details: -; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html +; https://doc.qt.io/qt-6/qtquickcontrols2-configuration.html [Controls] Style=Basic diff --git a/examples/tutorials/drumpad/initial_project/qtquickcontrols2.conf b/examples/tutorials/drumpad/initial_project/qtquickcontrols2.conf index 87a95d01..6c94a509 100644 --- a/examples/tutorials/drumpad/initial_project/qtquickcontrols2.conf +++ b/examples/tutorials/drumpad/initial_project/qtquickcontrols2.conf @@ -1,6 +1,6 @@ ; This file can be edited to change the style of the application ; Read "Qt Quick Controls 2 Configuration File" for details: -; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html +; https://doc.qt.io/qt-6/qtquickcontrols2-configuration.html [Controls] Style=Basic diff --git a/examples/tutorials/finance_manager/part1/Finance/qmldir b/examples/tutorials/finance_manager/part1/Finance/qmldir index 5362013c..b55a0959 100644 --- a/examples/tutorials/finance_manager/part1/Finance/qmldir +++ b/examples/tutorials/finance_manager/part1/Finance/qmldir @@ -4,3 +4,4 @@ FinanceView 1.0 FinanceView.qml FinancePieChart 1.0 FinancePieChart.qml FinanceDelegate 1.0 FinanceDelegate.qml AddDialog 1.0 AddDialog.qml +typeinfo financemodel.qmltypes diff --git a/examples/tutorials/finance_manager/part2/Finance/qmldir b/examples/tutorials/finance_manager/part2/Finance/qmldir index 5362013c..b55a0959 100644 --- a/examples/tutorials/finance_manager/part2/Finance/qmldir +++ b/examples/tutorials/finance_manager/part2/Finance/qmldir @@ -4,3 +4,4 @@ FinanceView 1.0 FinanceView.qml FinancePieChart 1.0 FinancePieChart.qml FinanceDelegate 1.0 FinanceDelegate.qml AddDialog 1.0 AddDialog.qml +typeinfo financemodel.qmltypes diff --git a/examples/tutorials/finance_manager/part2/database.py b/examples/tutorials/finance_manager/part2/database.py index 08cbb62c..80d40278 100644 --- a/examples/tutorials/finance_manager/part2/database.py +++ b/examples/tutorials/finance_manager/part2/database.py @@ -27,12 +27,13 @@ if env_db_path: db_path = Path(env_db_path) else: # Determine the application data directory based on the operating system using pathlib - if platform.system() == 'Windows': - app_data_location = Path(os.getenv('APPDATA')) / 'FinanceManager' - elif platform.system() == 'Darwin': # macOS - app_data_location = Path.home() / 'Library' / 'Application Support' / 'FinanceManager' - else: # Linux and other Unix-like systems - app_data_location = Path.home() / '.local' / 'share' / 'FinanceManager' + match platform.system(): + case 'Windows': + app_data_location = Path(os.getenv('APPDATA')) / 'FinanceManager' + case 'Darwin': # macOS + app_data_location = Path.home() / 'Library' / 'Application Support' / 'FinanceManager' + case _: # Linux and other Unix-like systems + app_data_location = Path.home() / '.local' / 'share' / 'FinanceManager' db_path = app_data_location / 'finances.db' diff --git a/examples/utils/pyside_config.py b/examples/utils/pyside_config.py index ea021030..ec4cb61d 100644 --- a/examples/utils/pyside_config.py +++ b/examples/utils/pyside_config.py @@ -53,6 +53,10 @@ options.append(("--shiboken-include-path", lambda: get_package_include_path(Package.SHIBOKEN_MODULE), pyside_error, "Print shiboken module include paths")) +options.append(("--shiboken-generator-include-path", + lambda: get_package_include_path(Package.SHIBOKEN_GENERATOR), + pyside_error, + "Print shiboken generator include paths")) options.append(("--pyside-include-path", lambda: get_package_include_path(Package.PYSIDE_MODULE), pyside_error, diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/ApplicationRoot.qml b/examples/webenginequick/nanobrowser/BrowserUtils/ApplicationRoot.qml index ec571620..8ffa2037 100644 --- a/examples/webenginequick/nanobrowser/BrowserUtils/ApplicationRoot.qml +++ b/examples/webenginequick/nanobrowser/BrowserUtils/ApplicationRoot.qml @@ -4,42 +4,10 @@ pragma ComponentBehavior: Bound import QtQuick -import QtWebEngine +import BrowserUtils QtObject { id: root - - property WebEngineProfilePrototype defaultProfilePrototype : WebEngineProfilePrototype { - storageName: "Profile" - Component.onCompleted: { - let fullVersionList = root.defaultProfilePrototype.instance().clientHints.fullVersionList; - fullVersionList["QuickNanoBrowser"] = "1.0"; - root.defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList; - } - } - - property WebEngineProfilePrototype otrPrototype : WebEngineProfilePrototype { - } - - property Component browserWindowComponent: BrowserWindow { - applicationRoot: root - } - property Component browserDialogComponent: BrowserDialog { - onClosing: destroy() - } - function createWindow(profile) { - var newWindow = browserWindowComponent.createObject(root) as BrowserWindow; - newWindow.currentWebView.profile = profile; - profile.downloadRequested.connect(newWindow.onDownloadRequested); - return newWindow; - } - function createDialog(profile) { - var newDialog = browserDialogComponent.createObject(root) as BrowserDialog; - newDialog.currentWebView.profile = profile; - return newDialog; - } - function load(url) { - var browserWindow = createWindow(root.defaultProfilePrototype.instance()); - browserWindow.currentWebView.url = url; - } + required property string startupUrl + Component.onCompleted: BrowserManager.load(startupUrl) } diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/BrowserManager.qml b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserManager.qml new file mode 100644 index 00000000..cef7621b --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserManager.qml @@ -0,0 +1,42 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma Singleton + +import QtQuick +import QtWebEngine + +QtObject { + id: manager + + property WebEngineProfilePrototype defaultProfilePrototype : WebEngineProfilePrototype { + storageName: "Profile" + } + + property WebEngineProfilePrototype otrPrototype : WebEngineProfilePrototype {} + + function createWindow(profile) { + let browserWindowComponent = Qt.createComponent("BrowserUtils", "BrowserWindow"); + let newWindow = browserWindowComponent.createObject(manager) as BrowserWindow; + newWindow.currentWebView.profile = profile; + profile.downloadRequested.connect(newWindow.onDownloadRequested); + return newWindow; + } + function createDialog(profile) { + let browserDialogComponent = Qt.createComponent("BrowserUtils", "BrowserDialog"); + let newDialog = browserDialogComponent.createObject(manager) as BrowserDialog; + newDialog.closing.connect(function(){destroy()}) + newDialog.currentWebView.profile = profile; + return newDialog; + } + function load(url) { + let browserWindow = createWindow(manager.defaultProfilePrototype.instance()); + browserWindow.currentWebView.url = url; + } + + Component.onCompleted: { + let fullVersionList = manager.defaultProfilePrototype.instance().clientHints.fullVersionList; + fullVersionList["QuickNanoBrowser"] = "1.0"; + manager.defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList; + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/BrowserWindow.qml b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserWindow.qml index c4e28b8e..e7e5238c 100644 --- a/examples/webenginequick/nanobrowser/BrowserUtils/BrowserWindow.qml +++ b/examples/webenginequick/nanobrowser/BrowserUtils/BrowserWindow.qml @@ -15,10 +15,8 @@ import BrowserUtils ApplicationWindow { id: win - required property QtObject applicationRoot property WebEngineView currentWebView: tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null property int previousVisibility: Window.Windowed - property int createdTabs: 0 property bool lastTabClosing: false width: 1300 @@ -33,25 +31,27 @@ ApplicationWindow { findBar.reset(); } - // When using style "mac", ToolButtons are not supposed to accept focus. - property bool platformIsMac: Qt.platform.os === "osx" - Settings { id : appSettings - property alias autoLoadImages: loadImages.checked - property alias javaScriptEnabled: javaScriptEnabled.checked - property alias errorPageEnabled: errorPageEnabled.checked - property alias pluginsEnabled: pluginsEnabled.checked - property alias fullScreenSupportEnabled: fullScreenSupportEnabled.checked - property alias autoLoadIconsForPage: autoLoadIconsForPage.checked - property alias touchIconsEnabled: touchIconsEnabled.checked - property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked - property alias devToolsEnabled: devToolsEnabled.checked - property alias pdfViewerEnabled: pdfViewerEnabled.checked - property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow - property alias javascriptCanAccessClipboard: javascriptCanAccessClipboard.checked - property alias javascriptCanPaste: javascriptCanPaste.checked - } + property alias autoLoadImages: navigationBar.loadImagesChecked + property alias javaScriptEnabled: navigationBar.javaScriptEnabledChecked + property alias errorPageEnabled: navigationBar.errorPageEnabledChecked + property alias pluginsEnabled: navigationBar.pluginsEnabledChecked + property alias fullScreenSupportEnabled: navigationBar.fullScreenSupportEnabledChecked + property alias autoLoadIconsForPage: navigationBar.autoLoadIconsForPageChecked + property alias touchIconsEnabled: navigationBar.touchIconsEnabledChecked + property alias webRTCPublicInterfacesOnly : navigationBar.webRTCPublicInterfacesOnlyChecked + property alias devToolsEnabled: navigationBar.devToolsEnabledChecked + property alias pdfViewerEnabled: navigationBar.pdfViewerEnabledChecked + property alias javascriptCanAccessClipboard: navigationBar.javascriptCanAccessClipboardChecked + property alias javascriptCanPaste: navigationBar.javascriptCanPasteChecked + property int imageAnimationPolicy: { + return navigationBar.animateImageOnceChecked ? WebEngineSettings.ImageAnimationPolicy.AnimateOnce : + navigationBar.allowImageAnimationChecked ? WebEngineSettings.ImageAnimationPolicy.Allow : + navigationBar.disableImageAnimationChecked ? WebEngineSettings.ImageAnimationPolicy.Disallow : + WebEngineSettings.ImageAnimationPolicy.AnimateOnce + } + } Action { shortcut: "Ctrl+D" @@ -60,11 +60,11 @@ ApplicationWindow { } } Action { - id: focus + id: focusAction shortcut: "Ctrl+L" onTriggered: { - addressBar.forceActiveFocus(); - addressBar.selectAll(); + navigationBar.addressBar.forceActiveFocus(); + navigationBar.addressBar.selectAll(); } } Action { @@ -79,9 +79,9 @@ ApplicationWindow { onTriggered: { tabBar.createTab(tabBar.count !== 0 ? win.currentWebView.profile - : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance()); - addressBar.forceActiveFocus(); - addressBar.selectAll(); + : BrowserManager.defaultProfilePrototype.instance()); + navigationBar.addressBar.forceActiveFocus(); + navigationBar.addressBar.selectAll(); } } Action { @@ -172,285 +172,9 @@ ApplicationWindow { onTriggered: findBar.findPrevious() } - menuBar: ToolBar { + menuBar: WebToolBar { id: navigationBar - RowLayout { - anchors.fill: parent - ToolButton { - enabled: win.currentWebView?.canGoBack || win.currentWebView?.canGoForward - onClicked: historyMenu.open() - text: qsTr("▼") - Menu { - id: historyMenu - Instantiator { - model: win.currentWebView?.history?.items - MenuItem { - required property var model - text: model.title - onTriggered: win.currentWebView.goBackOrForward(model.offset) - checkable: !enabled - checked: !enabled - enabled: model.offset - } - - onObjectAdded: function(index, object) { - historyMenu.insertItem(index, object) - } - onObjectRemoved: function(index, object) { - historyMenu.removeItem(object) - } - } - } - } - - ToolButton { - id: backButton - icon.source: "qrc:///icons/3rdparty/go-previous.png" - onClicked: win.currentWebView.goBack() - enabled: win.currentWebView?.canGoBack ?? false - activeFocusOnTab: !win.platformIsMac - } - ToolButton { - id: forwardButton - icon.source: "qrc:///icons/3rdparty/go-next.png" - onClicked: win.currentWebView.goForward() - enabled: win.currentWebView?.canGoForward ?? false - activeFocusOnTab: !win.platformIsMac - } - ToolButton { - id: reloadButton - icon.source: win.currentWebView?.loading - ? "qrc:///icons/3rdparty/process-stop.png" - : "qrc:///icons/3rdparty/view-refresh.png" - onClicked: win.currentWebView?.loading ? win.currentWebView.stop() : win.currentWebView.reload() - activeFocusOnTab: !win.platformIsMac - } - TextField { - id: addressBar - Image { - anchors.verticalCenter: addressBar.verticalCenter; - x: 5 - z: 2 - id: faviconImage - width: 16; height: 16 - sourceSize: Qt.size(width, height) - source: win.currentWebView?.icon ? win.currentWebView.icon : '' - } - MouseArea { - id: textFieldMouseArea - acceptedButtons: Qt.RightButton - anchors.fill: parent - onClicked: { - var textSelectionStartPos = addressBar.selectionStart; - var textSelectionEndPos = addressBar.selectionEnd; - textFieldContextMenu.open(); - addressBar.select(textSelectionStartPos, textSelectionEndPos); - } - Menu { - id: textFieldContextMenu - x: textFieldMouseArea.mouseX - y: textFieldMouseArea.mouseY - MenuItem { - text: qsTr("Cut") - onTriggered: addressBar.cut() - enabled: addressBar.selectedText.length > 0 - } - MenuItem { - text: qsTr("Copy") - onTriggered: addressBar.copy() - enabled: addressBar.selectedText.length > 0 - } - MenuItem { - text: qsTr("Paste") - onTriggered: addressBar.paste() - enabled: addressBar.canPaste - } - MenuItem { - text: qsTr("Delete") - onTriggered: addressBar.text = qsTr("") - enabled: addressBar.selectedText.length > 0 - } - MenuSeparator {} - MenuItem { - text: qsTr("Select All") - onTriggered: addressBar.selectAll() - enabled: addressBar.text.length > 0 - } - } - } - leftPadding: 26 - focus: true - Layout.fillWidth: true - Binding on text { - when: win.currentWebView - value: win.currentWebView.url - } - onAccepted: win.currentWebView.url = Utils.fromUserInput(text) - selectByMouse: true - } - ToolButton { - id: settingsMenuButton - text: qsTr("⋮") - onClicked: settingsMenu.open() - Menu { - id: settingsMenu - y: settingsMenuButton.height - MenuItem { - id: loadImages - text: "Autoload images" - checkable: true - checked: WebEngine.settings.autoLoadImages - } - MenuItem { - id: javaScriptEnabled - text: "JavaScript On" - checkable: true - checked: WebEngine.settings.javascriptEnabled - } - MenuItem { - id: errorPageEnabled - text: "ErrorPage On" - checkable: true - checked: WebEngine.settings.errorPageEnabled - } - MenuItem { - id: pluginsEnabled - text: "Plugins On" - checkable: true - checked: true - } - MenuItem { - id: fullScreenSupportEnabled - text: "FullScreen On" - checkable: true - checked: WebEngine.settings.fullScreenSupportEnabled - } - MenuItem { - id: offTheRecordEnabled - text: "Off The Record" - checkable: true - checked: win.currentWebView?.profile === (win.applicationRoot as ApplicationRoot).otrPrototype.instance() - onToggled: function() { - if (win.currentWebView) { - win.currentWebView.profile = offTheRecordEnabled.checked - ? (win.applicationRoot as ApplicationRoot).otrPrototype.instance() - : (win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance(); - } - } - } - MenuItem { - id: httpDiskCacheEnabled - text: "HTTP Disk Cache" - checkable: !win.currentWebView?.profile?.offTheRecord ?? false - checked: win.currentWebView?.profile.httpCacheType === WebEngineProfile.DiskHttpCache - onToggled: function() { - if (win.currentWebView) { - win.currentWebView.profile.httpCacheType = httpDiskCacheEnabled.checked - ? WebEngineProfile.DiskHttpCache - : WebEngineProfile.MemoryHttpCache; - } - } - } - MenuItem { - id: autoLoadIconsForPage - text: "Icons On" - checkable: true - checked: WebEngine.settings.autoLoadIconsForPage - } - MenuItem { - id: touchIconsEnabled - text: "Touch Icons On" - checkable: true - checked: WebEngine.settings.touchIconsEnabled - enabled: autoLoadIconsForPage.checked - } - MenuItem { - id: webRTCPublicInterfacesOnly - text: "WebRTC Public Interfaces Only" - checkable: true - checked: WebEngine.settings.webRTCPublicInterfacesOnly - } - MenuItem { - id: devToolsEnabled - text: "Open DevTools" - checkable: true - checked: false - } - MenuItem { - id: pdfViewerEnabled - text: "PDF Viewer Enabled" - checkable: true - checked: WebEngine.settings.pdfViewerEnabled - } - Menu { - id: imageAnimationPolicy - title: "Image Animation Policy" - - MenuItem { - id: disableImageAnimation - text: "Disable All Image Animation" - checkable: true - autoExclusive: true - checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Disallow - onTriggered: { - appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Disallow - } - } - - MenuItem { - id: allowImageAnimation - text: "Allow All Animated Images" - checkable: true - autoExclusive: true - checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Allow - onTriggered : { - appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Allow - } - } - - MenuItem { - id: animateImageOnce - text: "Animate Image Once" - checkable: true - autoExclusive: true - checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.AnimateOnce - onTriggered : { - appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.AnimateOnce - } - } - } - - MenuItem { - id: javascriptCanAccessClipboard - text: "JavaScript can access clipboard" - checkable: true - checked: WebEngine.settings.javascriptCanAccessClipboard - } - MenuItem { - id: javascriptCanPaste - text: "JavaScript can paste" - checkable: true - checked: WebEngine.settings.javascriptCanPaste - } - } - } - } - ProgressBar { - id: progressBar - height: 3 - anchors { - left: parent.left - top: parent.bottom - right: parent.right - leftMargin: parent.anchors.leftMargin - rightMargin: parent.anchors.rightMargin - } - background: Item {} - z: -2 - from: 0 - to: 100 - value: (win.currentWebView?.loadProgress < 100) ? win.currentWebView.loadProgress : 0 - } + currentWebView: win.currentWebView } StackLayout { @@ -458,7 +182,7 @@ ApplicationWindow { currentIndex: tabBar.currentIndex anchors.top: tabBar.bottom - anchors.bottom: devToolsView.top + anchors.bottom: devToolsWebEngineView.top anchors.left: parent.left anchors.right: parent.right } @@ -478,13 +202,13 @@ ApplicationWindow { color: tabButton.down ? tabButton.fillColor : tabButton.nonSelectedColor border.width: 1 border.color: tabButton.frameColor - implicitWidth: Math.max(text.width + 30, 80) - implicitHeight: Math.max(text.height + 10, 20) + implicitWidth: Math.max(tabText.width + 30, 80) + implicitHeight: Math.max(tabText.height + 10, 20) Rectangle { height: 1 ; width: parent.width ; color: tabButton.frameColor} Rectangle { height: parent.height ; width: 1; color: tabButton.frameColor} Rectangle { x: parent.width - 2; height: parent.height ; width: 1; color: tabButton.frameColor} Text { - id: text + id: tabText anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 6 @@ -509,7 +233,7 @@ ApplicationWindow { } } - onClicked: addressBar.text = (tabLayout.itemAt(TabBar.index) as WebEngineView).url; + onClicked: navigationBar.addressBar.text = (tabLayout.itemAt(TabBar.index) as WebEngineView).url; function closeTab() { tabBar.tryCloseView(TabBar.index); } @@ -521,11 +245,12 @@ ApplicationWindow { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - Component.onCompleted: createTab((win.applicationRoot as ApplicationRoot).defaultProfilePrototype.instance()) + Component.onCompleted: createTab(BrowserManager.defaultProfilePrototype.instance()) function createTab(profile, focusOnNewTab = true, url = undefined) { - var webview = tabComponent.createObject(tabLayout, {index: tabBar.count , profile: profile}); + var webview = tabComponent.createObject(tabLayout, {profile: profile}); var newTabButton = tabButtonComponent.createObject(tabBar, {tabTitle: Qt.binding(function () { return webview.title; })}); + webview.index = Qt.binding(function () { return newTabButton.TabBar.index; }) tabBar.addItem(newTabButton); if (focusOnNewTab) { tabBar.setCurrentIndex(tabBar.count - 1); @@ -554,7 +279,7 @@ ApplicationWindow { id: tabComponent WebEngineView { id: webEngineView - property int index; + property int index: 0 focus: true onLinkHovered: function(hoveredUrl) { @@ -571,13 +296,9 @@ ApplicationWindow { State { name: "FullScreen" PropertyChanges { - target: tabBar - visible: false - height: 0 - } - PropertyChanges { - target: navigationBar - visible: false + tabBar.visible: false + tabBar.height: 0 + navigationBar.visible: false } } ] @@ -615,17 +336,17 @@ ApplicationWindow { if (!request.userInitiated) console.warn("Blocked a popup window."); else if (request.destination === WebEngineNewWindowRequest.InNewTab) { - var tab = tabBar.createTab(win.currentWebView.profile, true, request.requestedUrl); + let tab = tabBar.createTab(win.currentWebView.profile, true, request.requestedUrl); tab.acceptAsNewWindow(request); } else if (request.destination === WebEngineNewWindowRequest.InNewBackgroundTab) { - var backgroundTab = tabBar.createTab(win.currentWebView.profile, false); + let backgroundTab = tabBar.createTab(win.currentWebView.profile, false); backgroundTab.acceptAsNewWindow(request); } else if (request.destination === WebEngineNewWindowRequest.InNewDialog) { - var dialog = (win.applicationRoot as ApplicationRoot).createDialog(win.currentWebView.profile); - dialog.win.currentWebView.acceptAsNewWindow(request); + let dialog = BrowserManager.createDialog(win.currentWebView.profile); + dialog.currentWebView.acceptAsNewWindow(request); } else { - var window = (win.applicationRoot as ApplicationRoot).createWindow(win.currentWebView.profile); - window.win.currentWebView.acceptAsNewWindow(request); + let window = BrowserManager.createWindow(win.currentWebView.profile); + window.currentWebView.acceptAsNewWindow(request); } } @@ -672,7 +393,7 @@ ApplicationWindow { } print("Render process exited with code " + exitCode + " " + status); - reloadTimer.running = true; + Qt.callLater(function() { win.currentWebView.reload() }) } onSelectClientCertificate: function(selection) { @@ -699,20 +420,12 @@ ApplicationWindow { onWebAuthUxRequested: function(request) { webAuthDialog.init(request); } - - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: win.currentWebView.reload() - } } } } WebEngineView { - id: devToolsView - visible: devToolsEnabled.checked + id: devToolsWebEngineView + visible: appSettings.devToolsEnabled height: visible ? 400 : 0 inspectedView: visible && tabBar.currentIndex < tabBar.count ? tabLayout.children[tabBar.currentIndex] : null anchors.left: parent.left @@ -723,128 +436,19 @@ ApplicationWindow { request.openIn(tab); } - Timer { - id: hideTimer - interval: 0 - running: false - repeat: false - onTriggered: devToolsEnabled.checked = false - } onWindowCloseRequested: function() { // Delay hiding for keep the inspectedView set to receive the ACK message of close. - hideTimer.running = true; + Qt.callLater(function() { appSettings.devToolsEnabled = false }) } } - Dialog { + SslDialog { id: sslDialog anchors.centerIn: parent - contentWidth: Math.max(mainTextForSSLDialog.width, detailedTextForSSLDialog.width) - contentHeight: mainTextForSSLDialog.height + detailedTextForSSLDialog.height - property var certErrors: [] - // fixme: icon! - // icon: StandardIcon.Warning - standardButtons: Dialog.No | Dialog.Yes - title: "Server's certificate not trusted" - contentItem: Item { - Label { - id: mainTextForSSLDialog - text: "Do you wish to continue?" - } - Text { - id: detailedTextForSSLDialog - anchors.top: mainTextForSSLDialog.bottom - text: "If you wish so, you may continue with an unverified certificate.\n" + - "Accepting an unverified certificate means\n" + - "you may not be connected with the host you tried to connect to.\n" + - "Do you wish to override the security check and continue?" - } - } - - onAccepted: { - certErrors.shift().acceptCertificate(); - presentError(); - } - onRejected: reject() - - function reject(){ - certErrors.shift().rejectCertificate(); - presentError(); - } - function enqueue(error){ - certErrors.push(error); - presentError(); - } - function presentError(){ - visible = certErrors.length > 0 - } } - Dialog { + PermissionDialog { id: permissionDialog anchors.centerIn: parent width: Math.min(win.width, win.height) / 3 * 2 - contentWidth: mainTextForPermissionDialog.width - contentHeight: mainTextForPermissionDialog.height - standardButtons: Dialog.No | Dialog.Yes - title: "Permission Request" - - property var permission; - - contentItem: Item { - Label { - id: mainTextForPermissionDialog - } - } - - onAccepted: permission.grant() - onRejected: permission.deny() - onVisibleChanged: { - if (visible) { - mainTextForPermissionDialog.text = questionForPermissionType(); - width = contentWidth + 20; - } - } - - function questionForPermissionType() { - var question = "Allow " + permission.origin + " to " - - switch (permission.permissionType) { - case WebEnginePermission.PermissionType.Geolocation: - question += "access your location information?"; - break; - case WebEnginePermission.PermissionType.MediaAudioCapture: - question += "access your microphone?"; - break; - case WebEnginePermission.PermissionType.MediaVideoCapture: - question += "access your webcam?"; - break; - case WebEnginePermission.PermissionType.MediaAudioVideoCapture: - question += "access your microphone and webcam?"; - break; - case WebEnginePermission.PermissionType.MouseLock: - question += "lock your mouse cursor?"; - break; - case WebEnginePermission.PermissionType.DesktopVideoCapture: - question += "capture video of your desktop?"; - break; - case WebEnginePermission.PermissionType.DesktopAudioVideoCapture: - question += "capture audio and video of your desktop?"; - break; - case WebEnginePermission.PermissionType.Notifications: - question += "show notification on your desktop?"; - break; - case WebEnginePermission.PermissionType.ClipboardReadWrite: - question += "read from and write to your clipboard?"; - break; - case WebEnginePermission.PermissionType.LocalFontsAccess: - question += "access the fonts stored on your machine?"; - break; - default: - question += "access unknown or unsupported permission type [" + permission.permissionType + "] ?"; - break; - } - - return question; - } } FullScreenNotification { @@ -860,6 +464,7 @@ ApplicationWindow { WebAuthDialog { id: webAuthDialog visible: false + width: Math.min(win.width, win.height) / 3 * 2 } MessageDialog { @@ -941,7 +546,7 @@ ApplicationWindow { return; } closeEvent.accepted = false - for (var i = 0; i < tabBar.count; i++) { + for (let i = 0; i < tabBar.count; i++) { tabBar.tryCloseView(i); } } diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/PermissionDialog.qml b/examples/webenginequick/nanobrowser/BrowserUtils/PermissionDialog.qml new file mode 100644 index 00000000..4b7136e4 --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/PermissionDialog.qml @@ -0,0 +1,73 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls +import QtWebEngine + +Dialog { + id: root + contentWidth: mainTextForPermissionDialog.width + contentHeight: mainTextForPermissionDialog.height + standardButtons: Dialog.No | Dialog.Yes + title: "Permission Request" + + property var permission + + contentItem: Item { + Label { + id: mainTextForPermissionDialog + } + } + + onAccepted: permission.grant() + onRejected: permission.deny() + onVisibleChanged: { + if (visible) { + mainTextForPermissionDialog.text = questionForPermissionType(); + width = contentWidth + 20; + } + } + + function questionForPermissionType() { + var question = "Allow " + permission.origin + " to "; + + switch (permission.permissionType) { + case WebEnginePermission.PermissionType.Geolocation: + question += "access your location information?"; + break; + case WebEnginePermission.PermissionType.MediaAudioCapture: + question += "access your microphone?"; + break; + case WebEnginePermission.PermissionType.MediaVideoCapture: + question += "access your webcam?"; + break; + case WebEnginePermission.PermissionType.MediaAudioVideoCapture: + question += "access your microphone and webcam?"; + break; + case WebEnginePermission.PermissionType.MouseLock: + question += "lock your mouse cursor?"; + break; + case WebEnginePermission.PermissionType.DesktopVideoCapture: + question += "capture video of your desktop?"; + break; + case WebEnginePermission.PermissionType.DesktopAudioVideoCapture: + question += "capture audio and video of your desktop?"; + break; + case WebEnginePermission.PermissionType.Notifications: + question += "show notification on your desktop?"; + break; + case WebEnginePermission.PermissionType.ClipboardReadWrite: + question += "read from and write to your clipboard?"; + break; + case WebEnginePermission.PermissionType.LocalFontsAccess: + question += "access the fonts stored on your machine?"; + break; + default: + question += "access unknown or unsupported permission type [" + permission.permissionType + "] ?"; + break; + } + + return question; + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/SslDialog.qml b/examples/webenginequick/nanobrowser/BrowserUtils/SslDialog.qml new file mode 100644 index 00000000..4c7a84ac --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/SslDialog.qml @@ -0,0 +1,50 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls + +Dialog { + id: root + contentWidth: Math.max(mainTextForSSLDialog.width, detailedTextForSSLDialog.width) + contentHeight: mainTextForSSLDialog.height + detailedTextForSSLDialog.height + property var certErrors: [] + // fixme: icon! + // icon: StandardIcon.Warning + standardButtons: Dialog.No | Dialog.Yes + title: "Server's certificate not trusted" + contentItem: Item { + Label { + id: mainTextForSSLDialog + text: "Do you wish to continue?" + } + Text { + id: detailedTextForSSLDialog + anchors.top: mainTextForSSLDialog.bottom + text: "If you wish so, you may continue with an unverified certificate.\n" + + "Accepting an unverified certificate means\n" + + "you may not be connected with the host you tried to connect to.\n" + + "Do you wish to override the security check and continue?" + } + } + + onAccepted: { + certErrors.shift().acceptCertificate(); + presentError(); + } + onRejected: reject() + + function reject() { + certErrors.shift().rejectCertificate(); + presentError(); + } + function enqueue(error) { + certErrors.push(error); + presentError(); + } + function presentError() { + visible = certErrors.length > 0; + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/WebAuthDialog.qml b/examples/webenginequick/nanobrowser/BrowserUtils/WebAuthDialog.qml index 8d5cf598..51d5c410 100644 --- a/examples/webenginequick/nanobrowser/BrowserUtils/WebAuthDialog.qml +++ b/examples/webenginequick/nanobrowser/BrowserUtils/WebAuthDialog.qml @@ -11,7 +11,6 @@ import QtWebEngine Dialog { id: webAuthDialog anchors.centerIn: parent - width: Math.min(parent.parent.width, parent.parent.height) / 3 * 2 contentWidth: verticalLayout.width +10; contentHeight: verticalLayout.height +10; standardButtons: Dialog.Cancel | Dialog.Apply @@ -79,7 +78,7 @@ Dialog { } ButtonGroup { - id : selectAccount; + id : selectAccountButtonGroup; exclusive: true; } @@ -144,7 +143,7 @@ Dialog { spacing : 5 RadioButton { text: selectAccountRepeaterColumn.modelData - ButtonGroup.group : webAuthDialog.selectAccount; + ButtonGroup.group : selectAccountButtonGroup; onClicked: function(){ webAuthDialog.selectAccount = text; } @@ -161,7 +160,7 @@ Dialog { selectAccountModel.clear(); var userNames = webAuthDialog.authrequest.userNames; - for (var i = 0; i < userNames.length; i++) { + for (let i = 0; i < userNames.length; i++) { selectAccountModel.append( {"name" : userNames[i]}); } pinLabel.visible = false; diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/WebToolBar.qml b/examples/webenginequick/nanobrowser/BrowserUtils/WebToolBar.qml new file mode 100644 index 00000000..e289d741 --- /dev/null +++ b/examples/webenginequick/nanobrowser/BrowserUtils/WebToolBar.qml @@ -0,0 +1,298 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtWebEngine + +ToolBar { + id: root + required property WebEngineView currentWebView + + // When using style "mac", ToolButtons are not supposed to accept focus. + property bool platformIsMac: Qt.platform.os === "osx" + + property alias loadImagesChecked: loadImages.checked + property alias javaScriptEnabledChecked: javaScriptEnabledMenuItem.checked + property alias errorPageEnabledChecked: errorPageEnabledMenuItem.checked + property alias pluginsEnabledChecked: pluginsEnabledMenuItem.checked + property alias fullScreenSupportEnabledChecked: fullScreenSupportEnabledMenuItem.checked + property alias autoLoadIconsForPageChecked: autoLoadIconsForPageMenuItem.checked + property alias touchIconsEnabledChecked: touchIconsEnabledMenuItem.checked + property alias webRTCPublicInterfacesOnlyChecked: webRTCPublicInterfacesOnlyMenuItem.checked + property alias devToolsEnabledChecked: devToolsEnabledMenuItem.checked + property alias pdfViewerEnabledChecked: pdfViewerEnabledMenuItem.checked + property alias javascriptCanAccessClipboardChecked: javascriptCanAccessClipboardMenuItem.checked + property alias javascriptCanPasteChecked: javascriptCanPasteMenuItem.checked + property alias disableImageAnimationChecked: disableImageAnimation.checked + property alias allowImageAnimationChecked: allowImageAnimation.checked + property alias animateImageOnceChecked: animateImageOnce.checked + property alias addressBar: addressBar + + RowLayout { + anchors.fill: parent + ToolButton { + enabled: root.currentWebView?.canGoBack || root.currentWebView?.canGoForward + onClicked: historyMenu.open() + text: qsTr("▼") + Menu { + id: historyMenu + Instantiator { + model: root.currentWebView?.history?.items + MenuItem { + required property var model + text: model.title + onTriggered: root.currentWebView.goBackOrForward(model.offset) + checkable: !enabled + checked: !enabled + enabled: model.offset + } + + onObjectAdded: function (index, object) { + historyMenu.insertItem(index, object); + } + onObjectRemoved: function (index, object) { + historyMenu.removeItem(object); + } + } + } + } + + ToolButton { + id: backButton + icon.source: "qrc:///icons/3rdparty/go-previous.png" + onClicked: root.currentWebView.goBack() + enabled: root.currentWebView?.canGoBack ?? false + activeFocusOnTab: !root.platformIsMac + } + ToolButton { + id: forwardButton + icon.source: "qrc:///icons/3rdparty/go-next.png" + onClicked: root.currentWebView.goForward() + enabled: root.currentWebView?.canGoForward ?? false + activeFocusOnTab: !root.platformIsMac + } + ToolButton { + id: reloadButton + icon.source: root.currentWebView?.loading ? "qrc:///icons/3rdparty/process-stop.png" : "qrc:///icons/3rdparty/view-refresh.png" + onClicked: root.currentWebView?.loading ? root.currentWebView.stop() : root.currentWebView.reload() + activeFocusOnTab: !root.platformIsMac + } + TextField { + id: addressBar + Image { + id: faviconImage + anchors.verticalCenter: addressBar.verticalCenter + x: 5 + z: 2 + width: 16 + height: 16 + sourceSize: Qt.size(width, height) + source: root.currentWebView?.icon ? root.currentWebView.icon : '' + } + MouseArea { + id: textFieldMouseArea + acceptedButtons: Qt.RightButton + anchors.fill: parent + onClicked: { + var textSelectionStartPos = addressBar.selectionStart; + var textSelectionEndPos = addressBar.selectionEnd; + textFieldContextMenu.open(); + addressBar.select(textSelectionStartPos, textSelectionEndPos); + } + Menu { + id: textFieldContextMenu + x: textFieldMouseArea.mouseX + y: textFieldMouseArea.mouseY + MenuItem { + text: qsTr("Cut") + onTriggered: addressBar.cut() + enabled: addressBar.selectedText.length > 0 + } + MenuItem { + text: qsTr("Copy") + onTriggered: addressBar.copy() + enabled: addressBar.selectedText.length > 0 + } + MenuItem { + text: qsTr("Paste") + onTriggered: addressBar.paste() + enabled: addressBar.canPaste + } + MenuItem { + text: qsTr("Delete") + onTriggered: addressBar.text = qsTr("") + enabled: addressBar.selectedText.length > 0 + } + MenuSeparator {} + MenuItem { + text: qsTr("Select All") + onTriggered: addressBar.selectAll() + enabled: addressBar.text.length > 0 + } + } + } + leftPadding: 26 + focus: true + Layout.fillWidth: true + Binding on text { + when: root.currentWebView + value: root.currentWebView.url + } + onAccepted: root.currentWebView.url = Utils.fromUserInput(text) + selectByMouse: true + } + ToolButton { + id: settingsMenuButton + text: qsTr("⋮") + onClicked: settingsMenu.open() + Menu { + id: settingsMenu + y: settingsMenuButton.height + MenuItem { + id: loadImages + text: "Autoload images" + checkable: true + checked: WebEngine.settings.autoLoadImages + } + MenuItem { + id: javaScriptEnabledMenuItem + text: "JavaScript On" + checkable: true + checked: WebEngine.settings.javascriptEnabled + } + MenuItem { + id: errorPageEnabledMenuItem + text: "ErrorPage On" + checkable: true + checked: WebEngine.settings.errorPageEnabled + } + MenuItem { + id: pluginsEnabledMenuItem + text: "Plugins On" + checkable: true + checked: true + } + MenuItem { + id: fullScreenSupportEnabledMenuItem + text: "FullScreen On" + checkable: true + checked: WebEngine.settings.fullScreenSupportEnabled + } + MenuItem { + id: offTheRecordEnabled + text: "Off The Record" + checkable: true + checked: root.currentWebView?.profile === BrowserManager.otrPrototype.instance() + onToggled: function () { + if (root.currentWebView) { + root.currentWebView.profile = offTheRecordEnabled.checked ? BrowserManager.otrPrototype.instance() : BrowserManager.defaultProfilePrototype.instance(); + } + } + } + MenuItem { + id: httpDiskCacheEnabled + text: "HTTP Disk Cache" + checkable: !root.currentWebView?.profile?.offTheRecord ?? false + checked: root.currentWebView?.profile.httpCacheType === WebEngineProfile.DiskHttpCache + onToggled: function () { + if (root.currentWebView) { + root.currentWebView.profile.httpCacheType = httpDiskCacheEnabled.checked ? WebEngineProfile.DiskHttpCache : WebEngineProfile.MemoryHttpCache; + } + } + } + MenuItem { + id: autoLoadIconsForPageMenuItem + text: "Icons On" + checkable: true + checked: WebEngine.settings.autoLoadIconsForPage + } + MenuItem { + id: touchIconsEnabledMenuItem + text: "Touch Icons On" + checkable: true + checked: WebEngine.settings.touchIconsEnabled + enabled: autoLoadIconsForPageMenuItem.checked + } + MenuItem { + id: webRTCPublicInterfacesOnlyMenuItem + text: "WebRTC Public Interfaces Only" + checkable: true + checked: WebEngine.settings.webRTCPublicInterfacesOnly + } + MenuItem { + id: devToolsEnabledMenuItem + text: "Open DevTools" + checkable: true + checked: false + } + MenuItem { + id: pdfViewerEnabledMenuItem + text: "PDF Viewer Enabled" + checkable: true + checked: WebEngine.settings.pdfViewerEnabled + } + Menu { + id: imageAnimationPolicyMenu + title: "Image Animation Policy" + + MenuItem { + id: disableImageAnimation + text: "Disable All Image Animation" + checkable: true + autoExclusive: true + checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Disallow + } + + MenuItem { + id: allowImageAnimation + text: "Allow All Animated Images" + checkable: true + autoExclusive: true + checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Allow + } + + MenuItem { + id: animateImageOnce + text: "Animate Image Once" + checkable: true + autoExclusive: true + checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.AnimateOnce + } + } + + MenuItem { + id: javascriptCanAccessClipboardMenuItem + text: "JavaScript can access clipboard" + checkable: true + checked: WebEngine.settings.javascriptCanAccessClipboard + } + MenuItem { + id: javascriptCanPasteMenuItem + text: "JavaScript can paste" + checkable: true + checked: WebEngine.settings.javascriptCanPaste + } + } + } + } + ProgressBar { + id: progressBar + height: 3 + anchors { + left: parent.left + top: parent.bottom + right: parent.right + leftMargin: parent.anchors.leftMargin + rightMargin: parent.anchors.rightMargin + } + background: Item {} + z: -2 + from: 0 + to: 100 + value: (root.currentWebView?.loadProgress < 100) ? root.currentWebView.loadProgress : 0 + } +} diff --git a/examples/webenginequick/nanobrowser/BrowserUtils/qmldir b/examples/webenginequick/nanobrowser/BrowserUtils/qmldir index d4d0abdc..c950e95b 100644 --- a/examples/webenginequick/nanobrowser/BrowserUtils/qmldir +++ b/examples/webenginequick/nanobrowser/BrowserUtils/qmldir @@ -1,9 +1,14 @@ module BrowserUtils ApplicationRoot 254.0 ApplicationRoot.qml BrowserDialog 254.0 BrowserDialog.qml +singleton BrowserManager 254.0 BrowserManager.qml BrowserWindow 254.0 BrowserWindow.qml DownloadView 254.0 DownloadView.qml FindBar 254.0 FindBar.qml FullScreenNotification 254.0 FullScreenNotification.qml +PermissionDialog 254.0 PermissionDialog.qml +SslDialog 254.0 SslDialog.qml WebAuthDialog 254.0 WebAuthDialog.qml +WebToolBar 254.0 WebToolBar.qml depends QtQuick +typeinfo main.qmltypes diff --git a/examples/webenginequick/nanobrowser/main.py b/examples/webenginequick/nanobrowser/main.py index df0dd5e9..732d076b 100644 --- a/examples/webenginequick/nanobrowser/main.py +++ b/examples/webenginequick/nanobrowser/main.py @@ -8,8 +8,8 @@ import sys from argparse import ArgumentParser, RawTextHelpFormatter from pathlib import Path -from PySide6.QtCore import (QCoreApplication, QFileInfo, QMetaObject, QObject, - QUrl, Slot, Q_ARG) +from PySide6.QtCore import (QCoreApplication, QFileInfo, QObject, + QUrl, Slot) from PySide6.QtQml import QQmlApplicationEngine, QmlElement, QmlSingleton from PySide6.QtGui import QGuiApplication from PySide6.QtWebEngineQuick import QtWebEngineQuick @@ -60,13 +60,12 @@ if __name__ == '__main__': app_args.extend(["--webEngineArgs", "--single-process"]) app = QGuiApplication(app_args) engine = QQmlApplicationEngine() + engine.setInitialProperties({"startupUrl": url}) engine.addImportPath(Path(__file__).parent) engine.loadFromModule("BrowserUtils", "ApplicationRoot") if not engine.rootObjects(): sys.exit(-1) - QMetaObject.invokeMethod(engine.rootObjects()[0], "load", Q_ARG("QVariant", url)) - exit_code = app.exec() del engine sys.exit(exit_code) diff --git a/examples/webenginequick/nanobrowser/nanobrowser.pyproject b/examples/webenginequick/nanobrowser/nanobrowser.pyproject index fb03fd6c..2ff54864 100644 --- a/examples/webenginequick/nanobrowser/nanobrowser.pyproject +++ b/examples/webenginequick/nanobrowser/nanobrowser.pyproject @@ -1,6 +1,7 @@ { "files": ["main.py", "BrowserUtils/ApplicationRoot.qml", - "BrowserUtils/BrowserDialog.qml", "BrowserUtils/BrowserWindow.qml", + "BrowserUtils/BrowserDialog.qml", "BrowserUtils/BrowserManager.qml", + "BrowserUtils/BrowserWindow.qml", "BrowserUtils/DownloadView.qml", "BrowserUtils/FindBar.qml", "BrowserUtils/FullScreenNotification.qml", "BrowserUtils/WebAuthDialog.qml", "BrowserUtils/qmldir", "resources.qrc"] diff --git a/examples/webenginequick/nanobrowser/qmldir b/examples/webenginequick/nanobrowser/qmldir deleted file mode 100644 index 036b2e22..00000000 --- a/examples/webenginequick/nanobrowser/qmldir +++ /dev/null @@ -1,10 +0,0 @@ -module BrowserUtils -prefer :/qt/qml/BrowserUtils/ -ApplicationRoot 254.0 ApplicationRoot.qml -BrowserDialog 254.0 BrowserDialog.qml -BrowserWindow 254.0 BrowserWindow.qml -DownloadView 254.0 DownloadView.qml -FindBar 254.0 FindBar.qml -FullScreenNotification 254.0 FullScreenNotification.qml -WebAuthDialog 254.0 WebAuthDialog.qml -depends QtQuick diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.py b/examples/webenginewidgets/simplebrowser/browserwindow.py index 1d27fcbc..1ce16385 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.py +++ b/examples/webenginewidgets/simplebrowser/browserwindow.py @@ -396,16 +396,17 @@ class BrowserWindow(QMainWindow): return navigation_bar def handle_web_action_enabled_changed(self, action, enabled): - if action == QWebEnginePage.WebAction.Back: - self._history_back_action.setEnabled(enabled) - elif action == QWebEnginePage.WebAction.Forward: - self._history_forward_action.setEnabled(enabled) - elif action == QWebEnginePage.WebAction.Reload: - self._reload_action.setEnabled(enabled) - elif action == QWebEnginePage.WebAction.Stop: - self._stop_action.setEnabled(enabled) - else: - print("Unhandled webActionChanged signal", file=sys.stderr) + match action: + case QWebEnginePage.WebAction.Back: + self._history_back_action.setEnabled(enabled) + case QWebEnginePage.WebAction.Forward: + self._history_forward_action.setEnabled(enabled) + case QWebEnginePage.WebAction.Reload: + self._reload_action.setEnabled(enabled) + case QWebEnginePage.WebAction.Stop: + self._stop_action.setEnabled(enabled) + case _: + print("Unhandled webActionChanged signal", file=sys.stderr) def handle_web_view_title_changed(self, title): off_the_record = self._profile.isOffTheRecord() diff --git a/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json b/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json index d81f5bf2..3c902ba6 100644 --- a/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json +++ b/examples/webenginewidgets/simplebrowser/data/3rdparty/qt_attribution.json @@ -6,9 +6,8 @@ "QtParts": [ "examples" ], "Description": "Selected icons from the Tango Icon Library", - "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", "Version": "0.8.90", - "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", + "DownloadLocation": "https://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", "LicenseId": "urn:dje:license:public-domain", "License": "Public Domain", "LicenseFile": "COPYING", diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.py b/examples/webenginewidgets/simplebrowser/downloadwidget.py index 39a11053..60ae7745 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.py +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.py @@ -74,33 +74,34 @@ class DownloadWidget(QFrame): state = self._download.state() progress_bar = self._ui.m_progressBar - if state == QWebEngineDownloadRequest.DownloadInProgress: - if total_bytes_v > 0: - progress = round(100 * received_bytes_v / total_bytes_v) - progress_bar.setValue(progress) - progress_bar.setDisabled(False) - fmt = f"%p% - {received_bytes} of {total_bytes} downloaded - {bytes_per_second}/s" + match state: + case QWebEngineDownloadRequest.DownloadInProgress: + if total_bytes_v > 0: + progress = round(100 * received_bytes_v / total_bytes_v) + progress_bar.setValue(progress) + progress_bar.setDisabled(False) + fmt = f"%p% - {received_bytes} of {total_bytes} downloaded - {bytes_per_second}/s" # noqa: E501 + progress_bar.setFormat(fmt) + else: + progress_bar.setValue(0) + progress_bar.setDisabled(False) + fmt = f"unknown size - {received_bytes} downloaded - {bytes_per_second}/s" + progress_bar.setFormat(fmt) + case QWebEngineDownloadRequest.DownloadCompleted: + progress_bar.setValue(100) + progress_bar.setDisabled(True) + fmt = f"completed - {received_bytes} downloaded - {bytes_per_second}/s" + progress_bar.setFormat(fmt) + case QWebEngineDownloadRequest.DownloadCancelled: + progress_bar.setValue(0) + progress_bar.setDisabled(True) + fmt = f"cancelled - {received_bytes} downloaded - {bytes_per_second}/s" progress_bar.setFormat(fmt) - else: + case QWebEngineDownloadRequest.DownloadInterrupted: progress_bar.setValue(0) - progress_bar.setDisabled(False) - fmt = f"unknown size - {received_bytes} downloaded - {bytes_per_second}/s" + progress_bar.setDisabled(True) + fmt = "interrupted: " + self._download.interruptReasonString() progress_bar.setFormat(fmt) - elif state == QWebEngineDownloadRequest.DownloadCompleted: - progress_bar.setValue(100) - progress_bar.setDisabled(True) - fmt = f"completed - {received_bytes} downloaded - {bytes_per_second}/s" - progress_bar.setFormat(fmt) - elif state == QWebEngineDownloadRequest.DownloadCancelled: - progress_bar.setValue(0) - progress_bar.setDisabled(True) - fmt = f"cancelled - {received_bytes} downloaded - {bytes_per_second}/s" - progress_bar.setFormat(fmt) - elif state == QWebEngineDownloadRequest.DownloadInterrupted: - progress_bar.setValue(0) - progress_bar.setDisabled(True) - fmt = "interrupted: " + self._download.interruptReasonString() - progress_bar.setFormat(fmt) if state == QWebEngineDownloadRequest.DownloadInProgress: self._ui.m_cancelButton.setIcon(self._cancel_icon) diff --git a/examples/webenginewidgets/simplebrowser/webauthdialog.py b/examples/webenginewidgets/simplebrowser/webauthdialog.py index 162c595d..ceb2593b 100644 --- a/examples/webenginewidgets/simplebrowser/webauthdialog.py +++ b/examples/webenginewidgets/simplebrowser/webauthdialog.py @@ -62,14 +62,15 @@ class WebAuthDialog(QDialog): def update_display(self): state = self.uxRequest.state() - if state == QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount: - self.setupSelectAccountUI() - elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin: - self.setupCollectPinUI() - elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection: - self.setupFinishCollectTokenUI() - elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed: - self.setupErrorUI() + match state: + case QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount: + self.setupSelectAccountUI() + case QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin: + self.setupCollectPinUI() + case QWebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection: + self.setupFinishCollectTokenUI() + case QWebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed: + self.setupErrorUI() self.adjustSize() @@ -145,16 +146,17 @@ class WebAuthDialog(QDialog): errorDetails = "" - if pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked: - errorDetails = self.tr("Internal User Verification Locked ") - elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.WrongPin: - errorDetails = self.tr("Wrong PIN") - elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.TooShort: - errorDetails = self.tr("Too Short") - elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters: - errorDetails = self.tr("Invalid Characters") - elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin: - errorDetails = self.tr("Same as current PIN") + match pinRequestInfo.error: + case QWebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked: + errorDetails = self.tr("Internal User Verification Locked ") + case QWebEngineWebAuthUxRequest.PinEntryError.WrongPin: + errorDetails = self.tr("Wrong PIN") + case QWebEngineWebAuthUxRequest.PinEntryError.TooShort: + errorDetails = self.tr("Too Short") + case QWebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters: + errorDetails = self.tr("Invalid Characters") + case QWebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin: + errorDetails = self.tr("Same as current PIN") if errorDetails: errorDetails += f" {pinRequestInfo.remainingAttempts} attempts remaining" @@ -184,40 +186,41 @@ class WebAuthDialog(QDialog): state = self.uxRequest.requestFailureReason() failure_reason = QWebEngineWebAuthUxRequest.RequestFailureReason - if state == failure_reason.Timeout: - error_description = self.tr("Request Timeout") - elif state == failure_reason.KeyNotRegistered: - error_description = self.tr("Key not registered") - elif state == failure_reason.KeyAlreadyRegistered: - error_description = self.tr("You already registered self device." - "Try again with device") - isVisibleRetry = True - elif state == failure_reason.SoftPinBlock: - error_description = self.tr( - "The security key is locked because the wrong PIN was entered too many times." - "To unlock it, remove and reinsert it.") - isVisibleRetry = True - elif state == failure_reason.HardPinBlock: - error_description = self.tr( - "The security key is locked because the wrong PIN was entered too many times." - " Yo'll need to reset the security key.") - elif state == failure_reason.AuthenticatorRemovedDuringPinEntry: - error_description = self.tr( - "Authenticator removed during verification. Please reinsert and try again") - elif state == failure_reason.AuthenticatorMissingResidentKeys: - error_description = self.tr("Authenticator doesn't have resident key support") - elif state == failure_reason.AuthenticatorMissingUserVerification: - error_description = self.tr("Authenticator missing user verification") - elif state == failure_reason.AuthenticatorMissingLargeBlob: - error_description = self.tr("Authenticator missing Large Blob support") - elif state == failure_reason.NoCommonAlgorithms: - error_description = self.tr("Authenticator missing Large Blob support") - elif state == failure_reason.StorageFull: - error_description = self.tr("Storage Full") - elif state == failure_reason.UserConsentDenied: - error_description = self.tr("User consent denied") - elif state == failure_reason.WinUserCancelled: - error_description = self.tr("User Cancelled Request") + match state: + case failure_reason.Timeout: + error_description = self.tr("Request Timeout") + case failure_reason.KeyNotRegistered: + error_description = self.tr("Key not registered") + case failure_reason.KeyAlreadyRegistered: + error_description = self.tr("You already registered self device." + "Try again with device") + isVisibleRetry = True + case failure_reason.SoftPinBlock: + error_description = self.tr( + "The security key is locked because the wrong PIN was entered too many times." + "To unlock it, remove and reinsert it.") + isVisibleRetry = True + case failure_reason.HardPinBlock: + error_description = self.tr( + "The security key is locked because the wrong PIN was entered too many times." + " Yo'll need to reset the security key.") + case failure_reason.AuthenticatorRemovedDuringPinEntry: + error_description = self.tr( + "Authenticator removed during verification. Please reinsert and try again") + case failure_reason.AuthenticatorMissingResidentKeys: + error_description = self.tr("Authenticator doesn't have resident key support") + case failure_reason.AuthenticatorMissingUserVerification: + error_description = self.tr("Authenticator missing user verification") + case failure_reason.AuthenticatorMissingLargeBlob: + error_description = self.tr("Authenticator missing Large Blob support") + case failure_reason.NoCommonAlgorithms: + error_description = self.tr("Authenticator missing Large Blob support") + case failure_reason.StorageFull: + error_description = self.tr("Storage Full") + case failure_reason.UserConsentDenied: + error_description = self.tr("User consent denied") + case failure_reason.WinUserCancelled: + error_description = self.tr("User Cancelled Request") self.uiWebAuthDialog.m_headingLabel.setText(error_heading) self.uiWebAuthDialog.m_description.setText(error_description) diff --git a/examples/webenginewidgets/simplebrowser/webview.py b/examples/webenginewidgets/simplebrowser/webview.py index 4b106acb..062d2b8c 100644 --- a/examples/webenginewidgets/simplebrowser/webview.py +++ b/examples/webenginewidgets/simplebrowser/webview.py @@ -85,14 +85,15 @@ class WebView(QWebEngineView): @Slot(QWebEnginePage.RenderProcessTerminationStatus, int) def _render_process_terminated(self, termStatus, statusCode): status = "" - if termStatus == QWebEnginePage.NormalTerminationStatus: - status = "Render process normal exit" - elif termStatus == QWebEnginePage.AbnormalTerminationStatus: - status = "Render process abnormal exit" - elif termStatus == QWebEnginePage.CrashedTerminationStatus: - status = "Render process crashed" - elif termStatus == QWebEnginePage.KilledTerminationStatus: - status = "Render process killed" + match termStatus: + case QWebEnginePage.NormalTerminationStatus: + status = "Render process normal exit" + case QWebEnginePage.AbnormalTerminationStatus: + status = "Render process abnormal exit" + case QWebEnginePage.CrashedTerminationStatus: + status = "Render process crashed" + case QWebEnginePage.KilledTerminationStatus: + status = "Render process killed" m = f"Render process exited with code: {statusCode:#x}\nDo you want to reload the page?" btn = QMessageBox.question(self.window(), status, m) diff --git a/examples/webview/webviewbrowser/browserwindow.py b/examples/webview/webviewbrowser/browserwindow.py new file mode 100644 index 00000000..609d1143 --- /dev/null +++ b/examples/webview/webviewbrowser/browserwindow.py @@ -0,0 +1,144 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QSize, QUrl, Qt, Slot +from PySide6.QtGui import QAction, QIcon, QKeySequence +from PySide6.QtWidgets import (QLineEdit, QMainWindow, QMenu, QProgressBar, + QToolButton, QVBoxLayout, QWidget) +from PySide6.QtWebView import QWebView, QWebViewLoadingInfo, QWebViewSettings + + +URL = "https://qt.io" + + +PROGRESS_STYLESHEET = "QProgressBar {border: 0px} QProgressBar.chunk {background-color: #da4453}" +SETTINGS_STYLESHEET = "QToolButton::menu-indicator {image: none;}" + + +SETTINGS = [ + (QWebViewSettings.WebAttribute.LocalStorageEnabled, "Enable Local storage"), + (QWebViewSettings.WebAttribute.JavaScriptEnabled, "Enable JavaScript"), + (QWebViewSettings.WebAttribute.AllowFileAccess, "Allow file access"), + (QWebViewSettings.WebAttribute.LocalContentCanAccessFileUrls, + "Enable file URLs for local documents") +] + + +class BrowserWindow(QMainWindow): + + def __init__(self): + super().__init__() + + # Set up toolbar + self._toolBar = self.addToolBar("Navigation") + self._toolBar.setMovable(False) + + self._backAction = QAction(QIcon(":/images/left-32.png"), "Go back", self) + self._backAction.setShortcut(QKeySequence.StandardKey.Back) + self._toolBar.addAction(self._backAction) + self._forwardAction = QAction(QIcon(":/images/right-32.png"), "Go forward", self) + self._forwardAction.setShortcut(QKeySequence.StandardKey.Forward) + self._toolBar.addAction(self._forwardAction) + self._reloadAction = QAction(QIcon(":/images/refresh-32.png"), "Reload", self) + self._reloadAction.setShortcut(QKeySequence.StandardKey.Refresh) + self._toolBar.addAction(self._reloadAction) + self._stopAction = QAction(QIcon(":/images/stop-32.png"), "Stop", self) + self._stopAction.setShortcut(QKeySequence.StandardKey.Cancel) + self._toolBar.addAction(self._stopAction) + + self._toolBar.addSeparator() + self._lineEdit = QLineEdit(self) + self._toolBar.addWidget(self._lineEdit) + + centralWidget = QWidget(self) + layout = QVBoxLayout(centralWidget) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + self.setCentralWidget(centralWidget) + + self._progressBar = QProgressBar() + self._progressBar.setTextVisible(False) + self._progressBar.setMaximumHeight(2) + self._progressBar.setStyleSheet(PROGRESS_STYLESHEET) + layout.addWidget(self._progressBar) + + # Wrap the QWebView in a QWidget + self._webView = QWebView() + webViewContainer = QWidget.createWindowContainer(self._webView) + webViewContainer.setMinimumSize(QSize(1280, 720)) + layout.addWidget(webViewContainer) + + settingsMenuButton = QToolButton() + settingsMenuButton.setToolTip("Settings") + settingsMenuButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) + settingsMenuButton.setStyleSheet(SETTINGS_STYLESHEET) + settingsMenuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) + settingsMenuButton.setArrowType(Qt.ArrowType.NoArrow) + settingsMenuButton.setIcon(QIcon(":/images/settings-32.png")) + self._toolBar.addSeparator() + self._toolBar.addWidget(settingsMenuButton) + settingsMenu = QMenu("Settings", settingsMenuButton) + settingsMenuButton.setMenu(settingsMenu) + settings = self._webView.settings() + for setting in SETTINGS: + action = settingsMenu.addAction(setting[1]) + attribute = setting[0] + action.setData(attribute) + action.setCheckable(True) + action.setChecked(settings.testAttribute(attribute)) + action.toggled.connect(self.onSettingToggled) + + self._stopAction.setVisible(False) + + # Set up signal/slot connections + self._backAction.triggered.connect(self._webView.goBack) + self._forwardAction.triggered.connect(self._webView.goForward) + self._reloadAction.triggered.connect(self._webView.reload) + self._stopAction.triggered.connect(self._webView.stop) + self._webView.loadingChanged.connect(self.onLoadingChanged) + self._webView.loadProgressChanged.connect(self.onLoadProgressChanged) + self._webView.titleChanged.connect(self.onTitleChanged) + self._lineEdit.returnPressed.connect(self.onUrlInput) + + # Load the startup URL + self._webView.setUrl(QUrl(URL)) + + @Slot(QWebViewLoadingInfo) + def onLoadingChanged(self, loadingInfo): + match loadingInfo.status(): + case QWebViewLoadingInfo.LoadStatus.Started: + self._reloadAction.setEnabled(False) + self._reloadAction.setVisible(False) + self._stopAction.setEnabled(True) + self._stopAction.setVisible(True) + case (QWebViewLoadingInfo.LoadStatus.Stopped | QWebViewLoadingInfo.LoadStatus.Succeeded + | QWebViewLoadingInfo.LoadStatus.Failed): + self._reloadAction.setEnabled(True) + self._reloadAction.setVisible(True) + self._stopAction.setEnabled(False) + self._stopAction.setVisible(False) + + self._backAction.setEnabled(self._webView.canGoBack()) + self._forwardAction.setEnabled(self._webView.canGoForward()) + self._lineEdit.setText(loadingInfo.url().toString()) + + @Slot(str) + def onTitleChanged(self, title): + self.setWindowTitle(title) + + @Slot(int) + def onLoadProgressChanged(self, loadProgress): + value = loadProgress if loadProgress > 0 and loadProgress < 100 else 0 + self._progressBar.setValue(value) + + @Slot() + def onUrlInput(self): + self._webView.setUrl(QUrl.fromUserInput(self._lineEdit.text())) + + @Slot(bool) + def onSettingToggled(self, checked): + settings = self._webView.settings() + action = self.sender() + attribute = action.data() + settings.setAttribute(attribute, action.isChecked()) + action.setChecked(settings.testAttribute(attribute)) diff --git a/examples/webview/webviewbrowser/doc/webviewbrowser.rst b/examples/webview/webviewbrowser/doc/webviewbrowser.rst new file mode 100644 index 00000000..36b5face --- /dev/null +++ b/examples/webview/webviewbrowser/doc/webviewbrowser.rst @@ -0,0 +1,8 @@ +WebView Browser Example +======================= + +Simple application that demonstrates how to use the +:class:`~PySide6.QtWebView.QWebView` class in a QWidget-based application. + +.. image:: webviewbrowser.webp + :alt: WebView Browser screenshot diff --git a/examples/webview/webviewbrowser/doc/webviewbrowser.webp b/examples/webview/webviewbrowser/doc/webviewbrowser.webp new file mode 100644 index 00000000..1f91f6f3 Binary files /dev/null and b/examples/webview/webviewbrowser/doc/webviewbrowser.webp differ diff --git a/examples/webview/webviewbrowser/images/left-32.png b/examples/webview/webviewbrowser/images/left-32.png new file mode 100644 index 00000000..16268853 Binary files /dev/null and b/examples/webview/webviewbrowser/images/left-32.png differ diff --git a/examples/webview/webviewbrowser/images/refresh-32.png b/examples/webview/webviewbrowser/images/refresh-32.png new file mode 100644 index 00000000..2e361735 Binary files /dev/null and b/examples/webview/webviewbrowser/images/refresh-32.png differ diff --git a/examples/webview/webviewbrowser/images/right-32.png b/examples/webview/webviewbrowser/images/right-32.png new file mode 100644 index 00000000..db19cb77 Binary files /dev/null and b/examples/webview/webviewbrowser/images/right-32.png differ diff --git a/examples/webview/webviewbrowser/images/settings-32.png b/examples/webview/webviewbrowser/images/settings-32.png new file mode 100644 index 00000000..9a87cd4d Binary files /dev/null and b/examples/webview/webviewbrowser/images/settings-32.png differ diff --git a/examples/webview/webviewbrowser/images/stop-32.png b/examples/webview/webviewbrowser/images/stop-32.png new file mode 100644 index 00000000..57bab6d0 Binary files /dev/null and b/examples/webview/webviewbrowser/images/stop-32.png differ diff --git a/examples/webview/webviewbrowser/main.py b/examples/webview/webviewbrowser/main.py new file mode 100644 index 00000000..b9fe566e --- /dev/null +++ b/examples/webview/webviewbrowser/main.py @@ -0,0 +1,14 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from PySide6.QtWidgets import QApplication + +import rc_webviewbrowser # noqa: F401 +from browserwindow import BrowserWindow + +if __name__ == "__main__": + app = QApplication(sys.argv) + browserWindow = BrowserWindow() + browserWindow.show() + sys.exit(app.exec()) diff --git a/examples/webview/webviewbrowser/rc_webviewbrowser.py b/examples/webview/webviewbrowser/rc_webviewbrowser.py new file mode 100644 index 00000000..b895e8b5 --- /dev/null +++ b/examples/webview/webviewbrowser/rc_webviewbrowser.py @@ -0,0 +1,189 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.10.2 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x01\x0f\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00 \x00\x00\x00 \x04\x03\x00\x00\x00\x81Tg\xc7\ +\x00\x00\x00$PLTE\x00\x00\x00\x9bs>\x9dq\ +>\x9fp>\xa3l?\xa5j?\xa8g@\xb1_A\ +\xbd@=\xbd==\xbfEE\xff\xff\xff\xc5m\x00\xe8\ +\x00\x00\x00\x09tRNS\x00\x020Et\x8f\xa9\xd8\ +\xfe\xba\x92\xb4\xd0\x00\x00\x00\x91IDAT(Sc\ +`\xc0\x0a\x98\xc2;g\x94* \xf8\x22\x9d3\x81`\ +\x86#\x8c\xcf\x0c\xe6\x03E\x0c\xa0\x02\x913\xa1`*\ +\x84\xcf6\x13\x0e\x12P\x15@\x950\xcfD\x02 S\ +$\x90\x05\x1a\x81\x02\x9e3g\xce\xda\x0d\x06+g\xce\ +\x9c\x02\x14\xe8D\x16\x98\x01t\xe4Ld\x81\x99\x0a\x0c\ +\xac\xa8\x02\x01\x0c\xec\xa8\x02\x05\x0c\x1c\xa8\x02\x0d\x0c\x9c\ +\xa8\x02\x130\x050\xb4`\x18\x8aa-\x86\xc30\x9c\ +\x8e\xe99\x0c\xefc\x04\x10F\x10b\x062F4`\ +F\x14FTbF62\x00\x005\xf5\xdew\x1c\xd0\ +O5\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x02\xbc\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\ +\x00\x00\x00\xd2PLTE\x00\x00\x00i\xa16i\xa1\ +6i\xa16i\xa16i\xa16i\xa16i\xa16\ +i\xa16i\xa16i\xa16i\xa16j\xa17k\ +\xa29l\xa3:m\xa3\xfb\x1b\xca\xb0E\x14Z\x91e\x92x.\x06\ +CH\xee\x1d\xcf\xa3\x1f\x96\x8f\xe3\x058\x92g\xfb\xbb\ +m\xe9\xdba\x0d\xdcB\xe2~{\xe7z\xc4\xec\xabn\ +,\xcbo9r\xd8\x11\x17/\x1f\x9b\xa3vD\x84J\ +\x83\x89D\x22\x02\x1e\x0c\xe6'n|\xdf\xf3\xbd\xb3\xfc\ +\xcd\xe0\x13\x98\xd2^X\xfc\xbb\x82\xec\xc7J\x82(\x0b\ +\x018\xc1\xc0%\xf9\x0d)\x86}@\x0a\x98\x1f\xfe9\ +\xe1\xfc\xbe7\x8a\xe1}\xb3G>Y\xda\x82\x02\x05\x9c\ +j\x84w-\xcf\xea\x04\x8dp\x08\x8d!\xe0\x10\xe1\x8a\ +\x89}\xb3\x85\x10\x87\x03x\x84T\xc2\xc0_\x87\xb8\xfc\ +\x84\x83G\x13\xf18@\x98j7\x8fp\xb8hs\x8a\ +7n\xf8>i[\xaf\x0b$\xa0\xae{\xd4n\x0d\x1a\ +0\xd7.\xd4\xd7\xa6\x02\x94h> \xe33\x9c\x12Z\ +\xc3\x81\x8a\xa0\x04&\xd92q>S\x89H\x1e\x08\xee\ +\x8d)I\xb2\xa9\x0e\xee\xaa\x1b\xe1U\x07J(\xb4M\ +2(\xd43\xfb\xb1)\x8d\x00?\x05\x83\xca\x14\xc3\xc9\ +\x06&\x19\xa4\x82;\x08F\x0d\xcb\x8a[\xe5\xe8\x936\ +W\xe1\xb2\x98b\xb8_\x98\xd2\x19\x0al\xc4p\xdd \ +\x18$M\x06%\xac\x8b\x8e\xea\x10a\xbc\x16b$\x98\ +@r\xe5\xa5\xa1_m\xe8\xcf\xdd\xb5\x13\xb2\xa5\xa2\xad\ +\x0d\xb7\xbae\xdf\xf739)\xdat\xd9\xa7?\x9c\xf4\ +\xa7\x97\xfex\xff\xb2/\x1aEL\xa3\xa3AOD\x00\ +\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x01\x8b\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\ +\x00\x00\x00ZPLTE\x00\x00\x00i\xa16i\xa1\ +6i\xa16i\xa16i\xa16i\xa16i\xa16\ +i\xa16i\xa16i\xa16i\xa16k\xa29l\ +\xa3:\x91\xbal\x9a\xc0y\x9e\xc2}\xa9\xc9\x8c\xb4\xd0\ +\x9b\xb5\xd0\x9c\xb6\xd1\x9e\xb7\xd2\x9f\xb9\xd2\xa1\xbd\xd5\xa7\ +\xce\xe0\xbd\xcf\xe0\xbf\xd0\xe1\xc0\xef\xf5\xeb\xfe\xfe\xfe\xff\ +\xff\xff\x10/\x92\xaf\x00\x00\x00\x0btRNS\x00\x01\ +\x19%CVk\x9b\xd5\xd7\xf7\x82Z2\x92\x00\x00\x00\ +\xd5IDAT8\xcb\x95S\x87\x12\x83 \x0cu@\ +\x11\xe8\xde#\xfc\xffo6D\x8d(\xd8\x5c\xdf\x9d\x02\ +y!\x9b\xaa\xfa\x03\x8d6\xd69ktSbk\xd5\ +yF\xa7\xea%\xdf&4\xa9\xb4s^\xfb\x0c:\xe5\ +7\xbe\x80\xcd\xcf\xfb3\x1b-\x8bN\x8f\x88\xfdx\x1c\ +\xe2\xa89\xbe;\x04\xc4s\xcb\x91\xf6\xb9(\xe6#\x1d\ +^\xbb\xc9\x89\x22\x85\xd1\xc0-\xe3}G\xf5\x1b\x0eW\ +\x88x\xa7\xbc\xf7\xcd\x94B\x91\xa7D\x0c\xed.\x00\x01\ +\xe0s<$@\xb1A\x05\xdb\xf3!\x03\xa0\xdc\xa2\x82\ +\xc3\xf5\x1c\xcd\x87\xfe\x0b\xbcE\xc2e\x0a\xf1\xe6B!\ +u\x01\xfc\x0b\xc0.8HX\x09rL\x93\xae\x95\xd2\ +\xe4B\x91\xe5B\xa1\xc4R'\xcd\xa28\xf2f\xa5\xed\ +\x8e\xc8\xda-\x0e\x8cn P^\xa0\xfd\xe8FX\x16\xdf\ +\xd3\x14\x14\xda\x0bL\x14\xb8\xf8\x04*N\xdd\xd5\x13\xa6\ +\x10\xa4a\x11\xc4#\x0a,Gl\x99\x1c\xb1\xc92\xcc\ +\x00\x14&\xdb\xe1\xe1\xa2\xc8\xab\xce\x93\x05\xfc*Y8\ +\xdd\xe0\x7f\x9dn\xb2`\xe8\x92\xa3\x8b\x96.{\xbaq\ +\xe8\xd6\xa3\x9b\xf7\x17\xbeU!)\xdf\x8e\x1d\xc3\x11\x00\ +\x00\x00\x00IEND\xaeB`\x82\ +" + +qt_resource_name = b"\ +\x00\x06\ +\x07\x03}\xc3\ +\x00i\ +\x00m\x00a\x00g\x00e\x00s\ +\x00\x0b\ +\x00\x90;'\ +\x00s\ +\x00t\x00o\x00p\x00-\x003\x002\x00.\x00p\x00n\x00g\ +\x00\x0e\ +\x07\xd4\xda\x07\ +\x00r\ +\x00e\x00f\x00r\x00e\x00s\x00h\x00-\x003\x002\x00.\x00p\x00n\x00g\ +\x00\x0b\ +\x00\x80-\xa7\ +\x00l\ +\x00e\x00f\x00t\x00-\x003\x002\x00.\x00p\x00n\x00g\ +\x00\x0f\ +\x07\x04,\xa7\ +\x00s\ +\x00e\x00t\x00t\x00i\x00n\x00g\x00s\x00-\x003\x002\x00.\x00p\x00n\x00g\ +\x00\x0c\ +\x0fz\xe9\xa7\ +\x00r\ +\x00i\x00g\x00h\x00t\x00-\x003\x002\x00.\x00p\x00n\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00P\x00\x00\x00\x00\x00\x01\x00\x00\x03\xd3\ +\x00\x00\x01\x9cB\xe4\xd3\xdc\ +\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x9cB\xe4\xd3\xdd\ +\x00\x00\x00l\x00\x00\x00\x00\x00\x01\x00\x00\x05b\ +\x00\x00\x01\x9cB\xe4\xd3\xdd\ +\x00\x00\x00.\x00\x00\x00\x00\x00\x01\x00\x00\x01\x13\ +\x00\x00\x01\x9cB\xe4\xd3\xdd\ +\x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x05\xd4\ +\x00\x00\x01\x9cB\xe4\xd3\xdd\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/webview/webviewbrowser/webviewbrowser.pyproject b/examples/webview/webviewbrowser/webviewbrowser.pyproject new file mode 100644 index 00000000..c53dba88 --- /dev/null +++ b/examples/webview/webviewbrowser/webviewbrowser.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py", "browserwindow.py", "webviewbrowser.qrc"] +} diff --git a/examples/webview/webviewbrowser/webviewbrowser.qrc b/examples/webview/webviewbrowser/webviewbrowser.qrc new file mode 100644 index 00000000..47e3132a --- /dev/null +++ b/examples/webview/webviewbrowser/webviewbrowser.qrc @@ -0,0 +1,9 @@ + + + images/stop-32.png + images/settings-32.png + images/right-32.png + images/refresh-32.png + images/left-32.png + + diff --git a/examples/widgetbinding/CMakeLists.txt b/examples/widgetbinding/CMakeLists.txt index acaa897d..455b538d 100644 --- a/examples/widgetbinding/CMakeLists.txt +++ b/examples/widgetbinding/CMakeLists.txt @@ -1,8 +1,8 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.18) -cmake_policy(VERSION 3.18) +cmake_minimum_required(VERSION 3.22) +cmake_policy(VERSION 3.22) # Enable policy to not use RPATH settings for install_name on macOS. if(POLICY CMP0068) diff --git a/examples/widgets/animation/easing/easing.py b/examples/widgets/animation/easing/easing.py index ccb29f38..9f074d32 100644 --- a/examples/widgets/animation/easing/easing.py +++ b/examples/widgets/animation/easing/easing.py @@ -24,13 +24,14 @@ class PathType(IntEnum): def createEasingCurve(curveType): curve = QEasingCurve(curveType) - if curveType == QEasingCurve.Type.BezierSpline: - curve.addCubicBezierSegment(QPointF(0.4, 0.1), QPointF(0.6, 0.9), QPointF(1.0, 1.0)) - elif curveType == QEasingCurve.Type.TCBSpline: - curve.addTCBSegment(QPointF(0.0, 0.0), 0, 0, 0) - curve.addTCBSegment(QPointF(0.3, 0.4), 0.2, 1, -0.2) - curve.addTCBSegment(QPointF(0.7, 0.6), -0.2, 1, 0.2) - curve.addTCBSegment(QPointF(1.0, 1.0), 0, 0, 0) + match curveType: + case QEasingCurve.Type.BezierSpline: + curve.addCubicBezierSegment(QPointF(0.4, 0.1), QPointF(0.6, 0.9), QPointF(1.0, 1.0)) + case QEasingCurve.Type.TCBSpline: + curve.addTCBSegment(QPointF(0.0, 0.0), 0, 0, 0) + curve.addTCBSegment(QPointF(0.3, 0.4), 0.2, 1, -0.2) + curve.addTCBSegment(QPointF(0.7, 0.6), -0.2, 1, 0.2) + curve.addTCBSegment(QPointF(1.0, 1.0), 0, 0, 0) return curve diff --git a/examples/widgets/animation/easing/easing.pyproject b/examples/widgets/animation/easing/easing.pyproject deleted file mode 100644 index 416b9979..00000000 --- a/examples/widgets/animation/easing/easing.pyproject +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files": ["easing.py", "form.ui"] -} diff --git a/examples/widgets/animation/easing/pyproject.toml b/examples/widgets/animation/easing/pyproject.toml new file mode 100644 index 00000000..3bc310d2 --- /dev/null +++ b/examples/widgets/animation/easing/pyproject.toml @@ -0,0 +1,5 @@ +[project] +name = "Easing Example" + +[tool.pyside6-project] +files = ["easing.py", "form.ui"] diff --git a/examples/widgets/dialogs/licensewizard/licensewizard.py b/examples/widgets/dialogs/licensewizard/licensewizard.py index 0fdeffbd..21a85353 100644 --- a/examples/widgets/dialogs/licensewizard/licensewizard.py +++ b/examples/widgets/dialogs/licensewizard/licensewizard.py @@ -266,30 +266,32 @@ class LicenseWizard(QWizard): self.last_help_message: str = None def show_help(self): - if self.currentId() == Pages.Page_Intro: - message = "The decision you make here will affect which page you get to see next." - - elif self.currentId() == Pages.Page_Evaluate: - message = ( - "Make sure to provide a valid email address, such as " - "toni.buddenbrook@example.de." - ) - - elif self.currentId() == Pages.Page_Register: - message = ( - "If you don't provide an upgrade key, you will be asked to fill in your details." - ) - - elif self.currentId() == Pages.Page_Details: - message = ( - "Make sure to provide a valid email address, such as " - "thomas.gradgrind@example.co.uk." - ) - - elif self.currentId() == Pages.Page_Conclusion: - message = "You must accept the terms and conditions of the license to proceed." - else: - message = "This help is likely not to be of any help." + match self.currentId(): + case Pages.Page_Intro: + message = "The decision you make here will affect which page you get to see next." + + case Pages.Page_Evaluate: + message = ( + "Make sure to provide a valid email address, such as " + "toni.buddenbrook@example.de." + ) + + case Pages.Page_Register: + message = ( + "If you don't provide an upgrade key, you will be asked to fill in " + "your details." + ) + + case Pages.Page_Details: + message = ( + "Make sure to provide a valid email address, such as " + "thomas.gradgrind@example.co.uk." + ) + + case Pages.Page_Conclusion: + message = "You must accept the terms and conditions of the license to proceed." + case _: + message = "This help is likely not to be of any help." if self.last_help_message == message: message = ( diff --git a/examples/widgets/dialogs/standarddialogs/standarddialogs.py b/examples/widgets/dialogs/standarddialogs/standarddialogs.py index fa773cfc..6dd2ffa3 100644 --- a/examples/widgets/dialogs/standarddialogs/standarddialogs.py +++ b/examples/widgets/dialogs/standarddialogs/standarddialogs.py @@ -389,13 +389,13 @@ class Dialog(QDialog): QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel) msg_box.setInformativeText(m) - reply = msg_box.exec() - if reply == QMessageBox.StandardButton.Yes: - self._question_label.setText("Yes") - elif reply == QMessageBox.StandardButton.No: - self._question_label.setText("No") - else: - self._question_label.setText("Cancel") + match msg_box.exec(): + case QMessageBox.StandardButton.Yes: + self._question_label.setText("Yes") + case QMessageBox.StandardButton.No: + self._question_label.setText("No") + case _: + self._question_label.setText("Cancel") @Slot() def warning_message(self): diff --git a/examples/widgets/draganddrop/dropsite/dropsitewindow.py b/examples/widgets/draganddrop/dropsite/dropsitewindow.py index 0d6bbebf..e25feb27 100644 --- a/examples/widgets/draganddrop/dropsite/dropsitewindow.py +++ b/examples/widgets/draganddrop/dropsite/dropsitewindow.py @@ -81,22 +81,23 @@ class DropSiteWindow(QWidget): format_item.setFlags(Qt.ItemFlag.ItemIsEnabled) format_item.setTextAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft) - if format == "text/plain": - text = simplify_whitespace(mime_data.text()) - elif format == "text/markdown": - text = mime_data.data("text/markdown").data().decode("utf8") - elif format == "text/html": - text = simplify_whitespace(mime_data.html()) - elif format == "text/uri-list": - url_list = mime_data.urls() - text = "" - for i in range(0, min(len(url_list), 32)): - text += url_list[i].toString() + " " - else: - data = mime_data.data(format) - if data.size() > 32: - data.truncate(32) - text = data.toHex(" ").data().decode("utf8").upper() + match format: + case "text/plain": + text = simplify_whitespace(mime_data.text()) + case "text/markdown": + text = mime_data.data("text/markdown").data().decode("utf8") + case "text/html": + text = simplify_whitespace(mime_data.html()) + case "text/uri-list": + url_list = mime_data.urls() + text = "" + for i in range(0, min(len(url_list), 32)): + text += url_list[i].toString() + " " + case _: + data = mime_data.data(format) + if data.size() > 32: + data.truncate(32) + text = data.toHex(" ").data().decode("utf8").upper() row = self._formats_table.rowCount() self._formats_table.insertRow(row) diff --git a/examples/widgets/graphicsview/diagramscene/diagramscene.py b/examples/widgets/graphicsview/diagramscene/diagramscene.py index 317113af..3ad0622f 100644 --- a/examples/widgets/graphicsview/diagramscene/diagramscene.py +++ b/examples/widgets/graphicsview/diagramscene/diagramscene.py @@ -153,29 +153,30 @@ class DiagramItem(QGraphicsPolygonItem): self._my_context_menu = contextMenu path = QPainterPath() - if self.diagram_type == self.StartEnd: - path.moveTo(200, 50) - path.arcTo(150, 0, 50, 50, 0, 90) - path.arcTo(50, 0, 50, 50, 90, 90) - path.arcTo(50, 50, 50, 50, 180, 90) - path.arcTo(150, 50, 50, 50, 270, 90) - path.lineTo(200, 25) - self._my_polygon = path.toFillPolygon() - elif self.diagram_type == self.Conditional: - self._my_polygon = QPolygonF([ - QPointF(-100, 0), QPointF(0, 100), - QPointF(100, 0), QPointF(0, -100), - QPointF(-100, 0)]) - elif self.diagram_type == self.Step: - self._my_polygon = QPolygonF([ - QPointF(-100, -100), QPointF(100, -100), - QPointF(100, 100), QPointF(-100, 100), - QPointF(-100, -100)]) - else: - self._my_polygon = QPolygonF([ - QPointF(-120, -80), QPointF(-70, 80), - QPointF(120, 80), QPointF(70, -80), - QPointF(-120, -80)]) + match self.diagram_type: + case self.StartEnd: + path.moveTo(200, 50) + path.arcTo(150, 0, 50, 50, 0, 90) + path.arcTo(50, 0, 50, 50, 90, 90) + path.arcTo(50, 50, 50, 50, 180, 90) + path.arcTo(150, 50, 50, 50, 270, 90) + path.lineTo(200, 25) + self._my_polygon = path.toFillPolygon() + case self.Conditional: + self._my_polygon = QPolygonF([ + QPointF(-100, 0), QPointF(0, 100), + QPointF(100, 0), QPointF(0, -100), + QPointF(-100, 0)]) + case self.Step: + self._my_polygon = QPolygonF([ + QPointF(-100, -100), QPointF(100, -100), + QPointF(100, 100), QPointF(-100, 100), + QPointF(-100, -100)]) + case _: + self._my_polygon = QPolygonF([ + QPointF(-120, -80), QPointF(-70, 80), + QPointF(120, 80), QPointF(70, -80), + QPointF(-120, -80)]) self.setPolygon(self._my_polygon) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True) @@ -284,36 +285,39 @@ class DiagramScene(QGraphicsScene): if (mouseEvent.button() != Qt.MouseButton.LeftButton): return - if self._my_mode == self.InsertItem: - item = DiagramItem(self._my_item_type, self._my_item_menu) - item.setBrush(self._my_item_color) - self.addItem(item) - item.setPos(mouseEvent.scenePos()) - self.item_inserted.emit(item) - elif self._my_mode == self.InsertLine: - self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), mouseEvent.scenePos())) - self.line.setPen(QPen(self._my_line_color, 2)) - self.addItem(self.line) - elif self._my_mode == self.InsertText: - text_item = DiagramTextItem() - text_item.setFont(self._my_font) - text_item.setTextInteractionFlags(Qt.TextInteractionFlag.TextEditorInteraction) - text_item.setZValue(1000.0) - text_item.lost_focus.connect(self.editor_lost_focus) - text_item.selected_change.connect(self.item_selected) - self.addItem(text_item) - text_item.setDefaultTextColor(self._my_text_color) - text_item.setPos(mouseEvent.scenePos()) - self.text_inserted.emit(text_item) + match self._my_mode: + case self.InsertItem: + item = DiagramItem(self._my_item_type, self._my_item_menu) + item.setBrush(self._my_item_color) + self.addItem(item) + item.setPos(mouseEvent.scenePos()) + self.item_inserted.emit(item) + case self.InsertLine: + self.line = QGraphicsLineItem(QLineF(mouseEvent.scenePos(), mouseEvent.scenePos())) + self.line.setPen(QPen(self._my_line_color, 2)) + self.addItem(self.line) + case self.InsertText: + text_item = DiagramTextItem() + text_item.setFont(self._my_font) + text_item.setTextInteractionFlags(Qt.TextInteractionFlag.TextEditorInteraction) + text_item.setZValue(1000.0) + text_item.lost_focus.connect(self.editor_lost_focus) + text_item.selected_change.connect(self.item_selected) + self.addItem(text_item) + text_item.setDefaultTextColor(self._my_text_color) + text_item.setPos(mouseEvent.scenePos()) + self.text_inserted.emit(text_item) super(DiagramScene, self).mousePressEvent(mouseEvent) def mouseMoveEvent(self, mouseEvent): - if self._my_mode == self.InsertLine and self.line: - new_line = QLineF(self.line.line().p1(), mouseEvent.scenePos()) - self.line.setLine(new_line) - elif self._my_mode == self.MoveItem: - super(DiagramScene, self).mouseMoveEvent(mouseEvent) + match self._my_mode: + case self.InsertItem: + if self.line: + new_line = QLineF(self.line.line().p1(), mouseEvent.scenePos()) + self.line.setLine(new_line) + case self.MoveItem: + super(DiagramScene, self).mouseMoveEvent(mouseEvent) def mouseReleaseEvent(self, mouseEvent): if self.line and self._my_mode == self.InsertLine: diff --git a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py index 3a051bd8..a7a20dcc 100644 --- a/examples/widgets/graphicsview/elasticnodes/elasticnodes.py +++ b/examples/widgets/graphicsview/elasticnodes/elasticnodes.py @@ -299,26 +299,25 @@ class GraphWidget(QGraphicsView): self._timer_id = self.startTimer(1000 / 25) def keyPressEvent(self, event): - key = event.key() - - if key == Qt.Key.Key_Up: - self._center_node.moveBy(0, -20) - elif key == Qt.Key.Key_Down: - self._center_node.moveBy(0, 20) - elif key == Qt.Key.Key_Left: - self._center_node.moveBy(-20, 0) - elif key == Qt.Key.Key_Right: - self._center_node.moveBy(20, 0) - elif key == Qt.Key.Key_Plus: - self.scale_view(1.2) - elif key == Qt.Key.Key_Minus: - self.scale_view(1 / 1.2) - elif key == Qt.Key.Key_Space or key == Qt.Key.Key_Enter: - for item in self.scene().items(): - if isinstance(item, Node): - item.setPos(-150 + random(300), -150 + random(300)) - else: - QGraphicsView.keyPressEvent(self, event) + match event.key(): + case Qt.Key.Key_Up: + self._center_node.moveBy(0, -20) + case Qt.Key.Key_Down: + self._center_node.moveBy(0, 20) + case Qt.Key.Key_Left: + self._center_node.moveBy(-20, 0) + case Qt.Key.Key_Right: + self._center_node.moveBy(20, 0) + case Qt.Key.Key_Plus: + self.scale_view(1.2) + case Qt.Key.Key_Minus: + self.scale_view(1 / 1.2) + case Qt.Key.Key_Space | Qt.Key.Key_Enter: + for item in self.scene().items(): + if isinstance(item, Node): + item.setPos(-150 + random(300), -150 + random(300)) + case _: + QGraphicsView.keyPressEvent(self, event) def timerEvent(self, event): nodes = [item for item in self.scene().items() if isinstance(item, Node)] diff --git a/examples/widgets/itemviews/address_book/adddialogwidget.py b/examples/widgets/itemviews/address_book/adddialogwidget.py index cd850bed..58c3a266 100644 --- a/examples/widgets/itemviews/address_book/adddialogwidget.py +++ b/examples/widgets/itemviews/address_book/adddialogwidget.py @@ -3,9 +3,9 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause from __future__ import annotations -from PySide6.QtCore import Qt -from PySide6.QtWidgets import (QDialog, QLabel, QTextEdit, QLineEdit, - QDialogButtonBox, QGridLayout, QVBoxLayout) +from PySide6.QtCore import Slot +from PySide6.QtWidgets import (QDialog, QFormLayout, QPlainTextEdit, QLineEdit, + QDialogButtonBox, QVBoxLayout) class AddDialogWidget(QDialog): @@ -14,31 +14,35 @@ class AddDialogWidget(QDialog): def __init__(self, parent=None): super().__init__(parent) - name_label = QLabel("Name") - address_label = QLabel("Address") - button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok - | QDialogButtonBox.StandardButton.Cancel) + self._button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok + | QDialogButtonBox.StandardButton.Cancel) self._name_text = QLineEdit() - self._address_text = QTextEdit() + self._address_text = QPlainTextEdit() - grid = QGridLayout() - grid.setColumnStretch(1, 2) - grid.addWidget(name_label, 0, 0) - grid.addWidget(self._name_text, 0, 1) - grid.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) - grid.addWidget(self._address_text, 1, 1, Qt.AlignmentFlag.AlignLeft) + formLayout = QFormLayout() + formLayout.addRow("Name", self._name_text) + formLayout.addRow("Address", self._address_text) - layout = QVBoxLayout() - layout.addLayout(grid) - layout.addWidget(button_box) - - self.setLayout(layout) + layout = QVBoxLayout(self) + layout.addLayout(formLayout) + layout.addWidget(self._button_box) self.setWindowTitle("Add a Contact") - button_box.accepted.connect(self.accept) - button_box.rejected.connect(self.reject) + self._button_box.accepted.connect(self.accept) + self._button_box.rejected.connect(self.reject) + self._name_text.textChanged.connect(self._updateEnabled) + self._address_text.textChanged.connect(self._updateEnabled) + + self._updateEnabled() + + @Slot() + def _updateEnabled(self): + name = self.name + address = self.address + enabled = bool(name) and name[:1].isalpha() and bool(address) + self._button_box.button(QDialogButtonBox.StandardButton.Ok).setEnabled(enabled) # These properties make using this dialog a little cleaner. It's much # nicer to type "addDialog.address" to retrieve the address as compared @@ -47,20 +51,22 @@ class AddDialogWidget(QDialog): def name(self): return self._name_text.text() - @property - def address(self): - return self._address_text.toPlainText() + @name.setter + def name(self, n): + self._name_text.setText(n) + @property + def name_enabled(self): + return self._name_text.isEnabled() -if __name__ == "__main__": - import sys - from PySide6.QtWidgets import QApplication + @name_enabled.setter + def name_enabled(self, e): + self._name_text.setEnabled(e) - app = QApplication(sys.argv) + @property + def address(self): + return self._address_text.toPlainText() - dialog = AddDialogWidget() - if (dialog.exec()): - name = dialog.name - address = dialog.address - print(f"Name: {name}") - print(f"Address: {address}") + @address.setter + def address(self, a): + self._address_text.setPlainText(a) diff --git a/examples/widgets/itemviews/address_book/address_book.py b/examples/widgets/itemviews/address_book/address_book.py deleted file mode 100644 index 64732690..00000000 --- a/examples/widgets/itemviews/address_book/address_book.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (C) 2011 Arun Srinivasan -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -from __future__ import annotations - -from PySide6.QtCore import Slot -from PySide6.QtGui import QAction -from PySide6.QtWidgets import (QMainWindow, QFileDialog, QApplication) - -from addresswidget import AddressWidget - - -class MainWindow(QMainWindow): - - def __init__(self, parent=None): - super().__init__(parent) - - self._address_widget = AddressWidget() - self.setCentralWidget(self._address_widget) - self.create_menus() - self.setWindowTitle("Address Book") - - def create_menus(self): - # Create the main menuBar menu items - file_menu = self.menuBar().addMenu("&File") - tool_menu = self.menuBar().addMenu("&Tools") - - # Populate the File menu - self.open_action = self.create_action("&Open...", file_menu, self.open_file) - self.save_action = self.create_action("&Save As...", file_menu, self.save_file) - file_menu.addSeparator() - self.exit_action = self.create_action("E&xit", file_menu, self.close) - - # Populate the Tools menu - self.add_action = self.create_action( - "&Add Entry...", tool_menu, self._address_widget.add_entry) - self._edit_action = self.create_action( - "&Edit Entry...", tool_menu, self._address_widget.edit_entry) - tool_menu.addSeparator() - self._remove_action = self.create_action( - "&Remove Entry", tool_menu, self._address_widget.remove_entry) - - # Disable the edit and remove menu items initially, as there are - # no items yet. - self._edit_action.setEnabled(False) - self._remove_action.setEnabled(False) - - # Wire up the updateActions slot - self._address_widget.selection_changed.connect(self.update_actions) - - def create_action(self, text, menu, slot): - """ Helper function to save typing when populating menus - with action. - """ - action = QAction(text, self) - menu.addAction(action) - action.triggered.connect(slot) - return action - - # Quick gotcha: - # - # QFiledialog.getOpenFilename and QFileDialog.get.SaveFileName don't - # behave in PySide6 as they do in Qt, where they return a QString - # containing the filename. - # - # In PySide6, these functions return a tuple: (filename, filter) - - @Slot() - def open_file(self): - filename, _ = QFileDialog.getOpenFileName(self) - if filename: - self._address_widget.read_from_file(filename) - - @Slot() - def save_file(self): - filename, _ = QFileDialog.getSaveFileName(self) - if filename: - self._address_widget.write_to_file(filename) - - def update_actions(self, selection): - """ Only allow the user to remove or edit an item if an item - is actually selected. - """ - indexes = selection.indexes() - - if len(indexes) > 0: - self._remove_action.setEnabled(True) - self._edit_action.setEnabled(True) - else: - self._remove_action.setEnabled(False) - self._edit_action.setEnabled(False) - - -if __name__ == "__main__": - """ Run the application. """ - import sys - app = QApplication(sys.argv) - mw = MainWindow() - mw.show() - sys.exit(app.exec()) diff --git a/examples/widgets/itemviews/address_book/address_book.pyproject b/examples/widgets/itemviews/address_book/address_book.pyproject index 1b5dd597..841bf196 100644 --- a/examples/widgets/itemviews/address_book/address_book.pyproject +++ b/examples/widgets/itemviews/address_book/address_book.pyproject @@ -1,4 +1,4 @@ { - "files": ["tablemodel.py", "address_book.py", "adddialogwidget.py", + "files": ["main.py", "tablemodel.py", "adddialogwidget.py", "addresswidget.py", "newaddresstab.py"] } diff --git a/examples/widgets/itemviews/address_book/addresswidget.py b/examples/widgets/itemviews/address_book/addresswidget.py index 37bd06f8..83651bd7 100644 --- a/examples/widgets/itemviews/address_book/addresswidget.py +++ b/examples/widgets/itemviews/address_book/addresswidget.py @@ -8,9 +8,9 @@ try: except ImportError: import pickle -from PySide6.QtCore import (Qt, Signal, Slot, QRegularExpression, QModelIndex, - QItemSelection, QSortFilterProxyModel) -from PySide6.QtWidgets import QTabWidget, QMessageBox, QTableView, QAbstractItemView +from PySide6.QtCore import (QItemSelection, QRegularExpression, QSortFilterProxyModel, + Qt, Signal, Slot) +from PySide6.QtWidgets import QAbstractItemView, QDialog, QMessageBox, QTableView, QTabWidget from tablemodel import TableModel from newaddresstab import NewAddressTab @@ -30,26 +30,24 @@ class AddressWidget(QTabWidget): self._table_model = TableModel() self._new_address_tab = NewAddressTab() - self._new_address_tab.send_details.connect(self.add_entry) + self._new_address_tab.triggered.connect(self.add_entry) self.addTab(self._new_address_tab, "Address Book") self.setup_tabs() @Slot() - def add_entry(self, name=None, address=None): + def add_entry(self): """ Add an entry to the addressbook. """ - if name is None and address is None: - add_dialog = AddDialogWidget() - - if add_dialog.exec(): - name = add_dialog.name - address = add_dialog.address + add_dialog = AddDialogWidget(self) + if add_dialog.exec() != QDialog.Accepted: + return - address = {"name": name, "address": address} + name = add_dialog.name + address = {"name": name, "address": add_dialog.address} addresses = self._table_model.addresses[:] - # The QT docs for this example state that what we're doing here + # The Qt docs for this example state that what we're doing here # is checking if the entered name already exists. What they # (and we here) are actually doing is checking if the whole # name/address pair exists already - ok for the purposes of this @@ -62,28 +60,30 @@ class AddressWidget(QTabWidget): except ValueError: # The address didn't already exist, so let's add it to the model. - # Step 1: create the row - self._table_model.insertRows(0) - - # Step 2: get the index of the newly created row and use it. - # to set the name - ix = self._table_model.index(0, 0, QModelIndex()) - self._table_model.setData(ix, address["name"], Qt.ItemDataRole.EditRole) - - # Step 3: lather, rinse, repeat for the address. - ix = self._table_model.index(0, 1, QModelIndex()) - self._table_model.setData(ix, address["address"], Qt.ItemDataRole.EditRole) + self._add_entry(address) # Remove the newAddressTab, as we now have at least one # address in the model. self.removeTab(self.indexOf(self._new_address_tab)) - # The screenshot for the QT example shows nicely formatted - # multiline cells, but the actual application doesn't behave - # quite so nicely, at least on Ubuntu. Here we resize the newly - # created row so that multiline addresses look reasonable. - table_view = self.currentWidget() - table_view.resizeRowToContents(ix.row()) + first_char = name[0:1].upper() + for t in range(self.count()): + if first_char in self.tabText(t)[0:1]: + self.setCurrentIndex(t) + break + + def _add_entry(self, address): + # Step 1: create the row + self._table_model.insertRows(0) + + # Step 2: get the index of the newly created row and use it. + # to set the name + ix = self._table_model.index(0, 0) + self._table_model.setData(ix, address["name"], Qt.ItemDataRole.EditRole) + + # Step 3: lather, rinse, repeat for the address. + ix = self._table_model.index(0, 1) + self._table_model.setData(ix, address["address"], Qt.ItemDataRole.EditRole) @Slot() def edit_entry(self): @@ -98,24 +98,24 @@ class AddressWidget(QTabWidget): return row = proxy_model.mapToSource(indexes[0]).row() - ix = self._table_model.index(row, 0, QModelIndex()) + ix = self._table_model.index(row, 0) name = self._table_model.data(ix, Qt.ItemDataRole.DisplayRole) - ix = self._table_model.index(row, 1, QModelIndex()) + ix = self._table_model.index(row, 1) address = self._table_model.data(ix, Qt.ItemDataRole.DisplayRole) # Open an addDialogWidget, and only allow the user to edit the address. - add_dialog = AddDialogWidget() + add_dialog = AddDialogWidget(self) add_dialog.setWindowTitle("Edit a Contact") - add_dialog._name_text.setReadOnly(True) - add_dialog._name_text.setText(name) - add_dialog._address_text.setText(address) + add_dialog.name_enabled = False + add_dialog.name = name + add_dialog.address = address # If the address is different, add it to the model. if add_dialog.exec(): new_address = add_dialog.address if new_address != address: - ix = self._table_model.index(row, 1, QModelIndex()) + ix = self._table_model.index(row, 1) self._table_model.setData(ix, new_address, Qt.ItemDataRole.EditRole) @Slot() @@ -127,10 +127,11 @@ class AddressWidget(QTabWidget): # Just like editEntry, but this time remove the selected row. indexes = selection_model.selectedRows() + if len(indexes) != 1: + return - for index in indexes: - row = proxy_model.mapToSource(index).row() - self._table_model.removeRows(row) + row = proxy_model.mapToSource(indexes[0]).row() + self._table_model.removeRows(row) # If we've removed the last address in the model, display the # newAddressTab @@ -167,14 +168,11 @@ class AddressWidget(QTabWidget): proxy_model.setFilterKeyColumn(0) # Filter on the "name" column proxy_model.sort(0, Qt.SortOrder.AscendingOrder) - # This prevents an application crash (see: - # https://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) # noqa: E501 - self.viewselectionmodel = table_view.selectionModel() table_view.selectionModel().selectionChanged.connect(self.selection_changed) self.addTab(table_view, group) - # Note: the QT example uses a QDataStream for the saving and loading. + # Note: the Qt example uses a QDataStream for the saving and loading. # Here we're using a python dictionary to store the addresses, which # can't be streamed using QDataStream, so we just use cpickle for this # example. @@ -188,11 +186,13 @@ class AddressWidget(QTabWidget): finally: f.close() - if len(addresses) == 0: - QMessageBox.information(self, f"No contacts in file: {filename}") + for address in addresses: + self._add_entry(address) + + if addresses: + self.removeTab(self.indexOf(self._new_address_tab)) else: - for address in addresses: - self.add_entry(address["name"], address["address"]) + QMessageBox.information(self, f"No contacts in file: {filename}") def write_to_file(self, filename): """ Save all contacts in the model to a file. """ @@ -204,13 +204,3 @@ class AddressWidget(QTabWidget): QMessageBox.information(self, f"Unable to open file: {filename}") finally: f.close() - - -if __name__ == "__main__": - import sys - from PySide6.QtWidgets import QApplication - - app = QApplication(sys.argv) - address_widget = AddressWidget() - address_widget.show() - sys.exit(app.exec()) diff --git a/examples/widgets/itemviews/address_book/main.py b/examples/widgets/itemviews/address_book/main.py new file mode 100644 index 00000000..2272cb92 --- /dev/null +++ b/examples/widgets/itemviews/address_book/main.py @@ -0,0 +1,106 @@ +# Copyright (C) 2011 Arun Srinivasan +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +from __future__ import annotations + +import sys +from PySide6.QtCore import QStandardPaths, Qt, Slot +from PySide6.QtGui import QAction, QIcon, QKeySequence +from PySide6.QtWidgets import QMainWindow, QFileDialog, QApplication + +from addresswidget import AddressWidget + + +FILTER = "Data files (*.dat)" + + +class MainWindow(QMainWindow): + + def __init__(self, parent=None): + super().__init__(parent) + + self._address_widget = AddressWidget() + self.setCentralWidget(self._address_widget) + self.create_menus() + self.setWindowTitle("Address Book") + + def create_menus(self): + # Create the main menuBar menu items + file_menu = self.menuBar().addMenu("&File") + tool_menu = self.menuBar().addMenu("&Tools") + + # Populate the File menu + self.open_action = QAction(QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen), "&Open...", self) + self.open_action.setShortcut(QKeySequence(QKeySequence.StandardKey.Open)) + self.open_action.triggered.connect(self.open_file) + file_menu.addAction(self.open_action) + + self.save_action = QAction(QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave), "&Save As...", + self) + self.save_action.setShortcut(QKeySequence(QKeySequence.StandardKey.Save)) + self.save_action.triggered.connect(self.save_file) + file_menu.addAction(self.save_action) + file_menu.addSeparator() + self.exit_action = QAction(QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit), "E&xit", self) + self.exit_action.setShortcut(QKeySequence(QKeySequence.StandardKey.Quit)) + self.exit_action.triggered.connect(self.close) + file_menu.addAction(self.exit_action) + + # Populate the Tools menu + self.add_action = tool_menu.addAction("&Add Entry...", self._address_widget.add_entry) + self.add_action.setShortcut(QKeySequence(Qt.KeyboardModifier.ControlModifier + | Qt.Key.Key_A)) + self._edit_action = tool_menu.addAction("&Edit Entry...", self._address_widget.edit_entry) + tool_menu.addSeparator() + self._remove_action = tool_menu.addAction("&Remove Entry", + self._address_widget.remove_entry) + + # Disable the edit and remove menu items initially, as there are + # no items yet. + self._edit_action.setEnabled(False) + self._remove_action.setEnabled(False) + + # Wire up the updateActions slot + self._address_widget.selection_changed.connect(self.update_actions) + + # Quick gotcha: + # + # QFiledialog.getOpenFilename and QFileDialog.get.SaveFileName don't + # behave in PySide6 as they do in Qt, where they return a QString + # containing the filename. + # + # In PySide6, these functions return a tuple: (filename, filter) + + @Slot() + def open_file(self): + dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DocumentsLocation) + filename, _ = QFileDialog.getOpenFileName(self, "Open File", dir, FILTER) + if filename: + self._address_widget.read_from_file(filename) + self.statusBar().showMessage(f"Read {filename}") + + @Slot() + def save_file(self): + dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DocumentsLocation) + filename, _ = QFileDialog.getSaveFileName(self, "Save File As", dir, FILTER) + if filename: + self._address_widget.write_to_file(filename) + self.statusBar().showMessage(f"Wrote {filename}") + + def update_actions(self, selection): + """ Only allow the user to remove or edit an item if an item + is actually selected. + """ + enabled = bool(selection.indexes()) + self._remove_action.setEnabled(enabled) + self._edit_action.setEnabled(enabled) + + +if __name__ == "__main__": + """ Run the application. """ + app = QApplication(sys.argv) + mw = MainWindow() + availableGeometry = mw.screen().availableGeometry() + mw.resize(availableGeometry.width() / 3, availableGeometry.height() / 3) + mw.show() + sys.exit(app.exec()) diff --git a/examples/widgets/itemviews/address_book/newaddresstab.py b/examples/widgets/itemviews/address_book/newaddresstab.py index 41410176..5ae5d4a0 100644 --- a/examples/widgets/itemviews/address_book/newaddresstab.py +++ b/examples/widgets/itemviews/address_book/newaddresstab.py @@ -3,10 +3,8 @@ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause from __future__ import annotations -from PySide6.QtCore import (Qt, Signal) -from PySide6.QtWidgets import (QWidget, QLabel, QPushButton, QVBoxLayout) - -from adddialogwidget import AddDialogWidget +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout class NewAddressTab(QWidget): @@ -14,7 +12,7 @@ class NewAddressTab(QWidget): To be displayed only when there are no contacts in the model. """ - send_details = Signal(str, str) + triggered = Signal() def __init__(self, parent=None): super().__init__(parent) @@ -24,34 +22,8 @@ class NewAddressTab(QWidget): add_button = QPushButton("Add") - layout = QVBoxLayout() - layout.addWidget(description_label) + layout = QVBoxLayout(self) + layout.addWidget(description_label, 0, Qt.AlignmentFlag.AlignCenter) layout.addWidget(add_button, 0, Qt.AlignmentFlag.AlignCenter) - self.setLayout(layout) - - add_button.clicked.connect(self.add_entry) - - def add_entry(self): - add_dialog = AddDialogWidget() - - if add_dialog.exec(): - name = add_dialog.name - address = add_dialog.address - self.send_details.emit(name, address) - - -if __name__ == "__main__": - - def print_address(name, address): - print(f"Name: {name}") - print(f"Address: {address}") - - import sys - from PySide6.QtWidgets import QApplication - - app = QApplication(sys.argv) - new_address_tab = NewAddressTab() - new_address_tab.send_details.connect(print_address) - new_address_tab.show() - sys.exit(app.exec()) + add_button.clicked.connect(self.triggered) diff --git a/examples/widgets/itemviews/address_book/tablemodel.py b/examples/widgets/itemviews/address_book/tablemodel.py index 6654edfa..b438da25 100644 --- a/examples/widgets/itemviews/address_book/tablemodel.py +++ b/examples/widgets/itemviews/address_book/tablemodel.py @@ -10,11 +10,7 @@ class TableModel(QAbstractTableModel): def __init__(self, addresses=None, parent=None): super().__init__(parent) - - if addresses is None: - self.addresses = [] - else: - self.addresses = addresses + self.addresses = addresses if addresses is not None else [] def rowCount(self, index=QModelIndex()): """ Returns the number of rows the model holds. """ @@ -26,37 +22,27 @@ class TableModel(QAbstractTableModel): def data(self, index, role=Qt.ItemDataRole.DisplayRole): """ Depending on the index and role given, return data. If not - returning data, return None (PySide equivalent of QT's + returning data, return None (PySide equivalent of Qt's "invalid QVariant"). """ - if not index.isValid(): - return None - - if not 0 <= index.row() < len(self.addresses): - return None - - if role == Qt.ItemDataRole.DisplayRole: - name = self.addresses[index.row()]["name"] - address = self.addresses[index.row()]["address"] - - if index.column() == 0: - return name - elif index.column() == 1: - return address - + if index.isValid() and role == Qt.ItemDataRole.DisplayRole: + row = index.row() + if 0 <= row < len(self.addresses): + match index.column(): + case 0: + return self.addresses[row]["name"] + case 1: + return self.addresses[row]["address"] return None def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole): """ Set the headers to be displayed. """ - if role != Qt.ItemDataRole.DisplayRole: - return None - - if orientation == Qt.Orientation.Horizontal: - if section == 0: - return "Name" - elif section == 1: - return "Address" - + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + match section: + case 0: + return "Name" + case 1: + return "Address" return None def insertRows(self, position, rows=1, index=QModelIndex()): @@ -82,19 +68,18 @@ class TableModel(QAbstractTableModel): """ Adjust the data (set it to ) depending on the given index and role. """ - if role != Qt.ItemDataRole.EditRole: + if not index.isValid() or role != Qt.ItemDataRole.EditRole: return False - if index.isValid() and 0 <= index.row() < len(self.addresses): - address = self.addresses[index.row()] - if index.column() == 0: - address["name"] = value - elif index.column() == 1: - address["address"] = value - else: - return False - - self.dataChanged.emit(index, index, 0) + row = index.row() + if 0 <= row < len(self.addresses): + address = self.addresses[row] + match index.column(): + case 0: + address["name"] = value + case 1: + address["address"] = value + self.dataChanged.emit(index, index, [Qt.ItemDataRole.EditRole.value]) return True return False diff --git a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py index 759f1ae1..be55f7f7 100644 --- a/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py +++ b/examples/widgets/itemviews/basicfiltermodel/basicsortfiltermodel.py @@ -4,17 +4,55 @@ from __future__ import annotations import sys -from PySide6.QtCore import (QDate, QDateTime, QRegularExpression, +from enum import Enum +from PySide6.QtCore import (QDate, QDateTime, QRangeModel, QRegularExpression, QSortFilterProxyModel, QTime, Qt, Slot) -from PySide6.QtGui import QStandardItemModel -from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout, +from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QTreeView, QVBoxLayout, QWidget) -REGULAR_EXPRESSION = 0 -WILDCARD = 1 -FIXED_STRING = 2 +class Syntax(Enum): + REGULAR_EXPRESSION = 0 + WILDCARD = 1 + FIXED_STRING = 2 + + +HEADERS = ["Subject", "Sender", "Date"] + + +MAILS = [ + ["RE: Sports", "Petra Schmidt ", + QDateTime(QDate(2007, 1, 5), QTime(12, 1))], + ["AW: Sports", "Rolf Newschweinstein ", + QDateTime(QDate(2007, 1, 5), QTime(12, 0))], + ["Sports", "Linda Smith ", + QDateTime(QDate(2007, 1, 5), QTime(11, 33))], + ["Re: Accounts", "Andy ", + QDateTime(QDate(2007, 1, 3), QTime(14, 26))], + ["Re: Accounts", "Joe Bloggs ", + QDateTime(QDate(2007, 1, 3), QTime(14, 18))], + ["Re: Expenses", "Andy ", + QDateTime(QDate(2007, 1, 2), QTime(16, 5))], + ["Expenses", "Joe Bloggs ", + QDateTime(QDate(2006, 12, 25), QTime(11, 39))], + ["Accounts", "pascale@nospam.com", + QDateTime(QDate(2006, 12, 31), QTime(12, 50))], + ["Radically new concept", "Grace K. ", + QDateTime(QDate(2006, 12, 22), QTime(9, 44))], + ["Happy New Year!", "Grace K. ", + QDateTime(QDate(2006, 12, 31), QTime(17, 3))] +] + + +class MailModel(QRangeModel): + def __init__(self, parent=None): + super().__init__(MAILS, parent) + + def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole): + if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole: + return HEADERS[section] + return None class Window(QWidget): @@ -42,25 +80,19 @@ class Window(QWidget): self._filter_pattern_line_edit = QLineEdit() self._filter_pattern_line_edit.setClearButtonEnabled(True) - self._filter_pattern_label = QLabel("&Filter pattern:") - self._filter_pattern_label.setBuddy(self._filter_pattern_line_edit) self._filter_syntax_combo_box = QComboBox() self._filter_syntax_combo_box.addItem("Regular expression", - REGULAR_EXPRESSION) + Syntax.REGULAR_EXPRESSION) self._filter_syntax_combo_box.addItem("Wildcard", - WILDCARD) + Syntax.WILDCARD) self._filter_syntax_combo_box.addItem("Fixed string", - FIXED_STRING) - self._filter_syntax_label = QLabel("Filter &syntax:") - self._filter_syntax_label.setBuddy(self._filter_syntax_combo_box) + Syntax.FIXED_STRING) self._filter_column_combo_box = QComboBox() - self._filter_column_combo_box.addItem("Subject") - self._filter_column_combo_box.addItem("Sender") - self._filter_column_combo_box.addItem("Date") + for header in HEADERS: + self._filter_column_combo_box.addItem(header) self._filter_column_label = QLabel("Filter &column:") - self._filter_column_label.setBuddy(self._filter_column_combo_box) self._filter_pattern_line_edit.textChanged.connect(self.filter_reg_exp_changed) self._filter_syntax_combo_box.currentIndexChanged.connect(self.filter_reg_exp_changed) @@ -68,29 +100,30 @@ class Window(QWidget): self._filter_case_sensitivity_check_box.toggled.connect(self.filter_reg_exp_changed) self._sort_case_sensitivity_check_box.toggled.connect(self.sort_changed) - source_layout = QHBoxLayout() + source_layout = QHBoxLayout(self._source_group_box) source_layout.addWidget(self._source_view) - self._source_group_box.setLayout(source_layout) - - proxy_layout = QGridLayout() - proxy_layout.addWidget(self._proxy_view, 0, 0, 1, 3) - proxy_layout.addWidget(self._filter_pattern_label, 1, 0) - proxy_layout.addWidget(self._filter_pattern_line_edit, 1, 1, 1, 2) - proxy_layout.addWidget(self._filter_syntax_label, 2, 0) - proxy_layout.addWidget(self._filter_syntax_combo_box, 2, 1, 1, 2) - proxy_layout.addWidget(self._filter_column_label, 3, 0) - proxy_layout.addWidget(self._filter_column_combo_box, 3, 1, 1, 2) - proxy_layout.addWidget(self._filter_case_sensitivity_check_box, 4, 0, 1, 2) - proxy_layout.addWidget(self._sort_case_sensitivity_check_box, 4, 2) - self._proxy_group_box.setLayout(proxy_layout) - - main_layout = QVBoxLayout() + + proxy_layout = QVBoxLayout(self._proxy_group_box) + proxy_layout.addWidget(self._proxy_view) + + form_layout = QFormLayout() + form_layout.addRow("&Filter pattern:", self._filter_pattern_line_edit) + form_layout.addRow("Filter &syntax:", self._filter_syntax_combo_box) + form_layout.addRow("Filter &column:", self._filter_column_combo_box) + proxy_layout.addLayout(form_layout) + + checkbox_layout = QHBoxLayout() + checkbox_layout.addWidget(self._filter_case_sensitivity_check_box) + checkbox_layout.addWidget(self._sort_case_sensitivity_check_box) + proxy_layout.addLayout(checkbox_layout) + + main_layout = QVBoxLayout(self) main_layout.addWidget(self._source_group_box) main_layout.addWidget(self._proxy_group_box) - self.setLayout(main_layout) self.setWindowTitle("Basic Sort/Filter Model") - self.resize(500, 450) + screen_geometry = self.screen().geometry() + self.resize(screen_geometry.width() / 2, screen_geometry.height() * 2 / 3) self._proxy_view.sortByColumn(1, Qt.SortOrder.AscendingOrder) self._filter_column_combo_box.setCurrentIndex(1) @@ -105,12 +138,12 @@ class Window(QWidget): @Slot() def filter_reg_exp_changed(self): - syntax_nr = self._filter_syntax_combo_box.currentData() pattern = self._filter_pattern_line_edit.text() - if syntax_nr == WILDCARD: - pattern = QRegularExpression.wildcardToRegularExpression(pattern) - elif syntax_nr == FIXED_STRING: - pattern = QRegularExpression.escape(pattern) + match self._filter_syntax_combo_box.currentData(): + case Syntax.WILDCARD: + pattern = QRegularExpression.wildcardToRegularExpression(pattern) + case Syntax.FIXED_STRING: + pattern = QRegularExpression.escape(pattern) reg_exp = QRegularExpression(pattern) if not self._filter_case_sensitivity_check_box.isChecked(): @@ -133,47 +166,9 @@ class Window(QWidget): self._proxy_model.setSortCaseSensitivity(case_sensitivity) -def add_mail(model, subject, sender, date): - model.insertRow(0) - model.setData(model.index(0, 0), subject) - model.setData(model.index(0, 1), sender) - model.setData(model.index(0, 2), date) - - -def create_mail_model(parent): - model = QStandardItemModel(0, 3, parent) - - model.setHeaderData(0, Qt.Orientation.Horizontal, "Subject") - model.setHeaderData(1, Qt.Orientation.Horizontal, "Sender") - model.setHeaderData(2, Qt.Orientation.Horizontal, "Date") - - add_mail(model, "Happy New Year!", "Grace K. ", - QDateTime(QDate(2006, 12, 31), QTime(17, 3))) - add_mail(model, "Radically new concept", "Grace K. ", - QDateTime(QDate(2006, 12, 22), QTime(9, 44))) - add_mail(model, "Accounts", "pascale@nospam.com", - QDateTime(QDate(2006, 12, 31), QTime(12, 50))) - add_mail(model, "Expenses", "Joe Bloggs ", - QDateTime(QDate(2006, 12, 25), QTime(11, 39))) - add_mail(model, "Re: Expenses", "Andy ", - QDateTime(QDate(2007, 1, 2), QTime(16, 5))) - add_mail(model, "Re: Accounts", "Joe Bloggs ", - QDateTime(QDate(2007, 1, 3), QTime(14, 18))) - add_mail(model, "Re: Accounts", "Andy ", - QDateTime(QDate(2007, 1, 3), QTime(14, 26))) - add_mail(model, "Sports", "Linda Smith ", - QDateTime(QDate(2007, 1, 5), QTime(11, 33))) - add_mail(model, "AW: Sports", "Rolf Newschweinstein ", - QDateTime(QDate(2007, 1, 5), QTime(12, 0))) - add_mail(model, "RE: Sports", "Petra Schmidt ", - QDateTime(QDate(2007, 1, 5), QTime(12, 1))) - - return model - - if __name__ == '__main__': app = QApplication(sys.argv) window = Window() - window.set_source_model(create_mail_model(window)) + window.set_source_model(MailModel(window)) window.show() sys.exit(app.exec()) diff --git a/examples/widgets/itemviews/fetchmore/fetchmore.py b/examples/widgets/itemviews/fetchmore/fetchmore.py index 8f41910a..22ac91b2 100644 --- a/examples/widgets/itemviews/fetchmore/fetchmore.py +++ b/examples/widgets/itemviews/fetchmore/fetchmore.py @@ -45,16 +45,17 @@ class FileListModel(QAbstractListModel): if row >= len(self._file_list) or row < 0: return None - if role == Qt.ItemDataRole.DisplayRole: - return self._file_list[row].fileName() + match role: + case Qt.ItemDataRole.DisplayRole: + return self._file_list[row].fileName() - if role == Qt.ItemDataRole.BackgroundRole: - batch = row // BATCH_SIZE - palette = qApp.palette() # noqa: F821 - return palette.base() if batch % 2 == 0 else palette.alternateBase() + case Qt.ItemDataRole.BackgroundRole: + batch = row // BATCH_SIZE + palette = qApp.palette() # noqa: F821 + return palette.base() if batch % 2 == 0 else palette.alternateBase() - if role == Qt.ItemDataRole.DecorationRole: - return self._icon_provider.icon(self._file_list[row]) + case Qt.ItemDataRole.DecorationRole: + return self._icon_provider.icon(self._file_list[row]) return None diff --git a/examples/widgets/itemviews/jsonmodel/jsonmodel.py b/examples/widgets/itemviews/jsonmodel/jsonmodel.py index 00ec1478..17263ebd 100644 --- a/examples/widgets/itemviews/jsonmodel/jsonmodel.py +++ b/examples/widgets/itemviews/jsonmodel/jsonmodel.py @@ -151,21 +151,20 @@ class JsonModel(QAbstractItemModel): Return data from a json item according index and role """ - if not index.isValid(): - return None - - item = index.internalPointer() - - if role == Qt.ItemDataRole.DisplayRole: - if index.column() == 0: - return item.key - - if index.column() == 1: - return item.value - - elif role == Qt.ItemDataRole.EditRole: - if index.column() == 1: - return item.value + ret = None + if index.isValid(): + item = index.internalPointer() + match role: + case Qt.ItemDataRole.DisplayRole: + match index.column(): + case 0: + ret = item.key + case 1: + ret = item.value + case Qt.ItemDataRole.EditRole: + if index.column() == 1: + ret = item.value + return ret def setData(self, index: QModelIndex, value: Any, role: Qt.ItemDataRole): """Override from QAbstractItemModel diff --git a/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py b/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py index 8f1f9b42..a248a21f 100644 --- a/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py +++ b/examples/widgets/itemviews/spreadsheet/spreadsheetitem.py @@ -79,31 +79,29 @@ class SpreadSheetItem(QTableWidgetItem): first_val = int(start.text()) if start else 0 second_val = int(end.text()) if start else 0 - if op == "sum": - sum = 0 - for r in range(first_row, second_row + 1): - for c in range(first_col, second_col + 1): - table_item = widget.item(r, c) - if table_item and table_item != this: - sum += int(table_item.text()) - - result = sum - elif op == "+": - result = first_val + second_val - elif op == "-": - result = first_val - second_val - elif op == "*": - result = first_val * second_val - elif op == "/": - if second_val == 0: - result = "nan" - else: - result = first_val / second_val - elif op == "=": - if start: - result = start.text() - else: - result = formula + match op: + case "sum": + sum = 0 + for r in range(first_row, second_row + 1): + for c in range(first_col, second_col + 1): + table_item = widget.item(r, c) + if table_item and table_item != this: + sum += int(table_item.text()) + + result = sum + case "+": + result = first_val + second_val + case "-": + result = first_val - second_val + case "*": + result = first_val * second_val + case "/": + result = first_val / second_val if second_val != 0 else "nan" + case "=": + if start: + result = start.text() + case _: + result = formula return result diff --git a/examples/widgets/painting/basicdrawing/basicdrawing.py b/examples/widgets/painting/basicdrawing/basicdrawing.py index a712c580..26bc9883 100644 --- a/examples/widgets/painting/basicdrawing/basicdrawing.py +++ b/examples/widgets/painting/basicdrawing/basicdrawing.py @@ -94,34 +94,35 @@ class RenderArea(QWidget): painter.scale(0.6, 0.9) painter.translate(-50, -50) - if self.shape == RenderArea.Line: - painter.drawLine(rect.bottomLeft(), rect.topRight()) - elif self.shape == RenderArea.Points: - painter.drawPoints(RenderArea.points) - elif self.shape == RenderArea.Polyline: - painter.drawPolyline(RenderArea.points) - elif self.shape == RenderArea.Polygon: - painter.drawPolygon(RenderArea.points) - elif self.shape == RenderArea.Rect: - painter.drawRect(rect) - elif self.shape == RenderArea.RoundedRect: - painter.drawRoundedRect(rect, 25, 25, Qt.SizeMode.RelativeSize) - elif self.shape == RenderArea.Ellipse: - painter.drawEllipse(rect) - elif self.shape == RenderArea.Arc: - painter.drawArc(rect, start_angle, arc_length) - elif self.shape == RenderArea.Chord: - painter.drawChord(rect, start_angle, arc_length) - elif self.shape == RenderArea.Pie: - painter.drawPie(rect, start_angle, arc_length) - elif self.shape == RenderArea.Path: - painter.drawPath(path) - elif self.shape == RenderArea.Text: - qv = qVersion() - painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, - f"PySide 6\nQt {qv}") - elif self.shape == RenderArea.Pixmap: - painter.drawPixmap(10, 10, self.pixmap) + match self.shape: + case RenderArea.Line: + painter.drawLine(rect.bottomLeft(), rect.topRight()) + case RenderArea.Points: + painter.drawPoints(RenderArea.points) + case RenderArea.Polyline: + painter.drawPolyline(RenderArea.points) + case RenderArea.Polygon: + painter.drawPolygon(RenderArea.points) + case RenderArea.Rect: + painter.drawRect(rect) + case RenderArea.RoundedRect: + painter.drawRoundedRect(rect, 25, 25, Qt.SizeMode.RelativeSize) + case RenderArea.Ellipse: + painter.drawEllipse(rect) + case RenderArea.Arc: + painter.drawArc(rect, start_angle, arc_length) + case RenderArea.Chord: + painter.drawChord(rect, start_angle, arc_length) + case RenderArea.Pie: + painter.drawPie(rect, start_angle, arc_length) + case RenderArea.Path: + painter.drawPath(path) + case RenderArea.Text: + qv = qVersion() + painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, + f"PySide 6\nQt {qv}") + case RenderArea.Pixmap: + painter.drawPixmap(10, 10, self.pixmap) painter.setPen(self.palette().dark().color()) painter.setBrush(Qt.BrushStyle.NoBrush) @@ -273,31 +274,31 @@ class Window(QWidget): self._render_area.set_pen(QPen(Qt.GlobalColor.blue, width, style, cap, join)) def brush_changed(self): - style = Qt.BrushStyle(self._brush_style_combo_box.itemData( - self._brush_style_combo_box.currentIndex(), id_role)) - - if style == Qt.BrushStyle.LinearGradientPattern: - linear_gradient = QLinearGradient(0, 0, 100, 100) - linear_gradient.setColorAt(0.0, Qt.GlobalColor.white) - linear_gradient.setColorAt(0.2, Qt.GlobalColor.green) - linear_gradient.setColorAt(1.0, Qt.GlobalColor.black) - self._render_area.set_brush(QBrush(linear_gradient)) - elif style == Qt.BrushStyle.RadialGradientPattern: - radial_gradient = QRadialGradient(50, 50, 50, 70, 70) - radial_gradient.setColorAt(0.0, Qt.GlobalColor.white) - radial_gradient.setColorAt(0.2, Qt.GlobalColor.green) - radial_gradient.setColorAt(1.0, Qt.GlobalColor.black) - self._render_area.set_brush(QBrush(radial_gradient)) - elif style == Qt.BrushStyle.ConicalGradientPattern: - conical_gradient = QConicalGradient(50, 50, 150) - conical_gradient.setColorAt(0.0, Qt.GlobalColor.white) - conical_gradient.setColorAt(0.2, Qt.GlobalColor.green) - conical_gradient.setColorAt(1.0, Qt.GlobalColor.black) - self._render_area.set_brush(QBrush(conical_gradient)) - elif style == Qt.BrushStyle.TexturePattern: - self._render_area.set_brush(QBrush(QPixmap(':/images/brick.png'))) - else: - self._render_area.set_brush(QBrush(Qt.GlobalColor.green, style)) + index = self._brush_style_combo_box.currentIndex() + style = Qt.BrushStyle(self._brush_style_combo_box.itemData(index, id_role)) + match style: + case Qt.BrushStyle.LinearGradientPattern: + linear_gradient = QLinearGradient(0, 0, 100, 100) + linear_gradient.setColorAt(0.0, Qt.GlobalColor.white) + linear_gradient.setColorAt(0.2, Qt.GlobalColor.green) + linear_gradient.setColorAt(1.0, Qt.GlobalColor.black) + self._render_area.set_brush(QBrush(linear_gradient)) + case Qt.BrushStyle.RadialGradientPattern: + radial_gradient = QRadialGradient(50, 50, 50, 70, 70) + radial_gradient.setColorAt(0.0, Qt.GlobalColor.white) + radial_gradient.setColorAt(0.2, Qt.GlobalColor.green) + radial_gradient.setColorAt(1.0, Qt.GlobalColor.black) + self._render_area.set_brush(QBrush(radial_gradient)) + case Qt.BrushStyle.ConicalGradientPattern: + conical_gradient = QConicalGradient(50, 50, 150) + conical_gradient.setColorAt(0.0, Qt.GlobalColor.white) + conical_gradient.setColorAt(0.2, Qt.GlobalColor.green) + conical_gradient.setColorAt(1.0, Qt.GlobalColor.black) + self._render_area.set_brush(QBrush(conical_gradient)) + case Qt.BrushStyle.TexturePattern: + self._render_area.set_brush(QBrush(QPixmap(':/images/brick.png'))) + case _: + self._render_area.set_brush(QBrush(Qt.GlobalColor.green, style)) if __name__ == '__main__': diff --git a/examples/widgets/richtext/textedit/textedit.py b/examples/widgets/richtext/textedit/textedit.py index 20ced2c5..d424aebb 100644 --- a/examples/widgets/richtext/textedit/textedit.py +++ b/examples/widgets/richtext/textedit/textedit.py @@ -350,15 +350,16 @@ class TextEdit(QMainWindow): db = QMimeDatabase() mime_type_name = db.mimeTypeForFileNameAndData(f, data).name() text = data.data().decode('utf8') - if mime_type_name == "text/html": - file_url = QUrl(f) if f[0] == ':' else QUrl.fromLocalFile(f) - options = QUrl.FormattingOptions(QUrl.UrlFormattingOption.RemoveFilename) - self._text_edit.document().setBaseUrl(file_url.adjusted(options)) - self._text_edit.setHtml(text) - elif mime_type_name == "text/markdown": - self._text_edit.setMarkdown(text) - else: - self._text_edit.setPlainText(text) + match mime_type_name: + case "text/html": + file_url = QUrl(f) if f[0] == ':' else QUrl.fromLocalFile(f) + options = QUrl.FormattingOptions(QUrl.UrlFormattingOption.RemoveFilename) + self._text_edit.document().setBaseUrl(file_url.adjusted(options)) + self._text_edit.setHtml(text) + case "text/markdown": + self._text_edit.setMarkdown(text) + case _: + self._text_edit.setPlainText(text) self.set_current_file_name(f) return True @@ -511,34 +512,35 @@ class TextEdit(QMainWindow): style = QTextListFormat.ListStyleUndefined marker = QTextBlockFormat.MarkerType.NoMarker - if styleIndex == 1: - style = QTextListFormat.ListDisc - elif styleIndex == 2: - style = QTextListFormat.ListCircle - elif styleIndex == 3: - style = QTextListFormat.ListSquare - elif styleIndex == 4: - if cursor.currentList(): - style = cursor.currentList().format().style() - else: - style = QTextListFormat.ListDisc - marker = QTextBlockFormat.MarkerType.Unchecked - elif styleIndex == 5: - if cursor.currentList(): - style = cursor.currentList().format().style() - else: + match styleIndex: + case 1: style = QTextListFormat.ListDisc - marker = QTextBlockFormat.MarkerType.Checked - elif styleIndex == 6: - style = QTextListFormat.ListDecimal - elif styleIndex == 7: - style = QTextListFormat.ListLowerAlpha - elif styleIndex == 8: - style = QTextListFormat.ListUpperAlpha - elif styleIndex == 9: - style = QTextListFormat.ListLowerRoman - elif styleIndex == 10: - style = QTextListFormat.ListUpperRoman + case 2: + style = QTextListFormat.ListCircle + case 3: + style = QTextListFormat.ListSquare + case 4: + if cursor.currentList(): + style = cursor.currentList().format().style() + else: + style = QTextListFormat.ListDisc + marker = QTextBlockFormat.MarkerType.Unchecked + case 5: + if cursor.currentList(): + style = cursor.currentList().format().style() + else: + style = QTextListFormat.ListDisc + marker = QTextBlockFormat.MarkerType.Checked + case 6: + style = QTextListFormat.ListDecimal + case 7: + style = QTextListFormat.ListLowerAlpha + case 8: + style = QTextListFormat.ListUpperAlpha + case 9: + style = QTextListFormat.ListLowerRoman + case 10: + style = QTextListFormat.ListUpperRoman cursor.beginEditBlock() @@ -648,34 +650,36 @@ class TextEdit(QMainWindow): def cursor_position_changed(self): self.alignment_changed(self._text_edit.alignment()) if current_list := self._text_edit.textCursor().currentList(): - style = current_list.format().style() - if style == QTextListFormat.ListDisc: - self._combo_style.setCurrentIndex(1) - elif style == QTextListFormat.ListCircle: - self._combo_style.setCurrentIndex(2) - elif style == QTextListFormat.ListSquare: - self._combo_style.setCurrentIndex(3) - elif style == QTextListFormat.ListDecimal: - self._combo_style.setCurrentIndex(6) - elif style == QTextListFormat.ListLowerAlpha: - self._combo_style.setCurrentIndex(7) - elif style == QTextListFormat.ListUpperAlpha: - self._combo_style.setCurrentIndex(8) - elif style == QTextListFormat.ListLowerRoman: - self._combo_style.setCurrentIndex(9) - elif style == QTextListFormat.ListUpperRoman: - self._combo_style.setCurrentIndex(10) - else: - self._combo_style.setCurrentIndex(-1) - marker = self._text_edit.textCursor().block().blockFormat().marker() - if marker == QTextBlockFormat.MarkerType.NoMarker: - self._action_toggle_check_state.setChecked(False) - elif marker == QTextBlockFormat.MarkerType.Unchecked: - self._combo_style.setCurrentIndex(4) - self._action_toggle_check_state.setChecked(False) - elif marker == QTextBlockFormat.MarkerType.Checked: - self._combo_style.setCurrentIndex(5) - self._action_toggle_check_state.setChecked(True) + match current_list.format().style(): + case QTextListFormat.ListDisc: + self._combo_style.setCurrentIndex(1) + case QTextListFormat.ListCircle: + self._combo_style.setCurrentIndex(2) + case QTextListFormat.ListSquare: + self._combo_style.setCurrentIndex(3) + case QTextListFormat.ListDecimal: + self._combo_style.setCurrentIndex(6) + case QTextListFormat.ListLowerAlpha: + self._combo_style.setCurrentIndex(7) + case QTextListFormat.ListUpperAlpha: + self._combo_style.setCurrentIndex(8) + case QTextListFormat.ListLowerRoman: + self._combo_style.setCurrentIndex(9) + case QTextListFormat.ListUpperRoman: + self._combo_style.setCurrentIndex(10) + case _: + self._combo_style.setCurrentIndex(-1) + + match self._text_edit.textCursor().block().blockFormat().marker(): + case QTextBlockFormat.MarkerType.NoMarker: + self._action_toggle_check_state.setChecked(False) + case QTextBlockFormat.MarkerType.Unchecked: + self._combo_style.setCurrentIndex(4) + self._action_toggle_check_state.setChecked(False) + case QTextBlockFormat.MarkerType.Checked: + self._combo_style.setCurrentIndex(5) + self._action_toggle_check_state.setChecked(True) + else: heading_level = self._text_edit.textCursor().blockFormat().headingLevel() new_level = heading_level + 10 if heading_level != 0 else 0 diff --git a/examples/widgets/widgets/tetrix/tetrix.py b/examples/widgets/widgets/tetrix/tetrix.py index 4428b332..8a9c2194 100644 --- a/examples/widgets/widgets/tetrix/tetrix.py +++ b/examples/widgets/widgets/tetrix/tetrix.py @@ -208,21 +208,21 @@ class TetrixBoard(QFrame): super(TetrixBoard, self).keyPressEvent(event) return - key = event.key() - if key == Qt.Key.Key_Left: - self.try_move(self._cur_piece, self._cur_x - 1, self._cur_y) - elif key == Qt.Key.Key_Right: - self.try_move(self._cur_piece, self._cur_x + 1, self._cur_y) - elif key == Qt.Key.Key_Down: - self.try_move(self._cur_piece.rotated_right(), self._cur_x, self._cur_y) - elif key == Qt.Key.Key_Up: - self.try_move(self._cur_piece.rotated_left(), self._cur_x, self._cur_y) - elif key == Qt.Key.Key_Space: - self.drop_down() - elif key == Qt.Key.Key_D: - self.one_line_down() - else: - super(TetrixBoard, self).keyPressEvent(event) + match event.key(): + case Qt.Key.Key_Left: + self.try_move(self._cur_piece, self._cur_x - 1, self._cur_y) + case Qt.Key.Key_Right: + self.try_move(self._cur_piece, self._cur_x + 1, self._cur_y) + case Qt.Key.Key_Down: + self.try_move(self._cur_piece.rotated_right(), self._cur_x, self._cur_y) + case Qt.Key.Key_Up: + self.try_move(self._cur_piece.rotated_left(), self._cur_x, self._cur_y) + case Qt.Key.Key_Space: + self.drop_down() + case Qt.Key.Key_D: + self.one_line_down() + case _: + super(TetrixBoard, self).keyPressEvent(event) def timerEvent(self, event): if event.timerId() == self.timer.timerId(): diff --git a/examples/xml/dombookmarks/jennifer.xbel b/examples/xml/dombookmarks/jennifer.xbel index d5042368..3c3c7a79 100644 --- a/examples/xml/dombookmarks/jennifer.xbel +++ b/examples/xml/dombookmarks/jennifer.xbel @@ -26,16 +26,13 @@ Forum.Qt.org - - The Independent Qt Tutorial - German Qt Forum Korean Qt Community Site - + Russian Qt Forum @@ -55,13 +52,6 @@ OneLook Dictionary Search - - BEOLINGUS, a service of TU Chemnitz - - - - Trésor de la Langue Française informatisé - Dictionnaire de l'Académie Française diff --git a/requirements-doc.txt b/requirements-doc.txt index a7d4d544..1f3d3ac6 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -6,9 +6,5 @@ sphinx-tags==0.4 sphinx-toolbox==3.7.0 sphinx-reredirects==0.1.5 myst-parser==3.0.1 -# FIXME: Using fork in order to enable the 'collapse_navbar=True' -# option for the sphinx-theme. Upstream proposal: -# https://github.com/pradyunsg/furo/pull/748#issuecomment-1895448722 -# furo==2023.9.10 -furo @ git+https://github.com/cmaureir/furo@add_collapse +furo==2025.12.19 graphviz==0.20.3 diff --git a/requirements.txt b/requirements.txt index feb5a01a..71000401 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,10 +2,10 @@ setuptools==78.1.0 packaging==24.2 build==1.2.2.post1 -wheel==0.43.0 +wheel==0.46.3 distro==1.9.0; sys_platform == 'linux' patchelf==0.17.2; sys_platform == 'linux' -# 2.0.2 is the last version that supports Python 3.9 -numpy<=2.0.2; python_version <= '3.9' -numpy==2.1.3; python_version > '3.9' +# 2.2.0 is the last version that supports Python 3.10 +numpy==2.2.0; python_version == '3.10' +numpy==2.4.6; python_version > '3.10' mypy>=1.15.0 # note: 3.13 with disable-gil is not compiled yet diff --git a/sources/pyside-tools/CMakeLists.txt b/sources/pyside-tools/CMakeLists.txt index cc9a7e5b..b8e9152e 100644 --- a/sources/pyside-tools/CMakeLists.txt +++ b/sources/pyside-tools/CMakeLists.txt @@ -1,7 +1,7 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -cmake_minimum_required(VERSION 3.18) +cmake_minimum_required(VERSION 3.22) project(pyside-tools) include(cmake/PySideToolsSetup.cmake) diff --git a/sources/pyside-tools/deploy.py b/sources/pyside-tools/deploy.py index b437b6da..2af38c4c 100644 --- a/sources/pyside-tools/deploy.py +++ b/sources/pyside-tools/deploy.py @@ -1,6 +1,5 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -from __future__ import annotations """ pyside6-deploy deployment tool @@ -28,16 +27,17 @@ from __future__ import annotations Note: This file is used by both pyside6-deploy and pyside6-android-deploy """ -import sys +from __future__ import annotations + import argparse import logging +import sys import traceback from pathlib import Path from textwrap import dedent -from deploy_lib import (MAJOR_VERSION, DesktopConfig, cleanup, config_option_exists, - finalize, create_config_file, PythonExecutable, Nuitka, - HELP_EXTRA_MODULES, HELP_EXTRA_IGNORE_DIRS) +from deploy_lib import (MAJOR_VERSION, DesktopConfig, cleanup, finalize, create_config_file, + PythonExecutable, Nuitka, add_deploy_arguments) TOOL_DESCRIPTION = dedent(f""" @@ -50,20 +50,11 @@ TOOL_DESCRIPTION = dedent(f""" Linux = .bin """) -HELP_MODE = dedent(""" - The mode in which the application is deployed. The options are: onefile, - standalone. The default value is onefile. - - This options translates to the mode Nuitka uses to create the executable. - - macOS by default uses the --standalone option. - """) - def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False, loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False, force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None, - mode: str = None) -> str | None: + mode: str = None, nuitka_version: str = None) -> str | None: """ Entry point for pyside6-deploy command. @@ -73,7 +64,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini logging.basicConfig(level=loglevel) # In case pyside6-deploy is run from a completely different location than the project directory - if main_file and main_file.exists(): + if main_file and main_file.exists() and not config_file: config_file = main_file.parent / "pysidedeploy.spec" if config_file and not config_file.exists() and not main_file.exists(): @@ -89,14 +80,8 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini if extra_ignore_dirs: extra_ignore_dirs = extra_ignore_dirs.split(",") - extra_modules = [] - if extra_modules_grouped: - tmp_extra_modules = extra_modules_grouped.split(",") - for extra_module in tmp_extra_modules: - if extra_module.startswith("Qt"): - extra_modules.append(extra_module[2:]) - else: - extra_modules.append(extra_module) + extra_modules = [m[2:] if m.startswith("Qt") else m + for m in extra_modules_grouped.split(",")] if extra_modules_grouped else [] python = PythonExecutable(dry_run=dry_run, init=init, force=force) config_file_exists = config_file and config_file.exists() @@ -112,7 +97,8 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini cleanup(config=config) - python.install_dependencies(config=config, packages="packages") + python.install_dependencies(config=config, packages="packages", + nuitka_version=nuitka_version) # required by Nuitka for pyenv Python add_arg = " --static-libpython=no" @@ -176,43 +162,14 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini if __name__ == "__main__": parser = argparse.ArgumentParser(description=TOOL_DESCRIPTION) + add_deploy_arguments(parser) - parser.add_argument("-c", "--config-file", type=lambda p: Path(p).absolute(), - default=(Path.cwd() / "pysidedeploy.spec"), - help="Path to the .spec config file") - - parser.add_argument( - type=lambda p: Path(p).absolute(), - help="Path to main python file", nargs="?", dest="main_file", - default=None if config_option_exists() else Path.cwd() / "main.py") - - parser.add_argument( - "--init", action="store_true", - help="Create pysidedeploy.spec file, if it doesn't already exists") - - parser.add_argument( - "-v", "--verbose", help="Run in verbose mode", action="store_const", - dest="loglevel", const=logging.INFO) - - parser.add_argument("--dry-run", action="store_true", help="Show the commands to be run") - - parser.add_argument( - "--keep-deployment-files", action="store_true", - help="Keep the generated deployment files generated") - - parser.add_argument("-f", "--force", action="store_true", help="Force all input prompts") - - parser.add_argument("--name", type=str, help="Application name") - - parser.add_argument("--extra-ignore-dirs", type=str, help=HELP_EXTRA_IGNORE_DIRS) - - parser.add_argument("--extra-modules", type=str, help=HELP_EXTRA_MODULES) - - parser.add_argument("--mode", choices=["onefile", "standalone"], default="onefile", - help=HELP_MODE) + parser.add_argument("--nuitka-version", type=str, default=None, + help=("Override the Nuitka version to install for deployment, " + "e.g. --nuitka-version=4.0")) args = parser.parse_args() main(args.main_file, args.name, args.config_file, args.init, args.loglevel, args.dry_run, args.keep_deployment_files, args.force, args.extra_ignore_dirs, args.extra_modules, - args.mode) + args.mode, args.nuitka_version) diff --git a/sources/pyside-tools/deploy_lib/__init__.py b/sources/pyside-tools/deploy_lib/__init__.py index 0f1f45ac..b8ae91ae 100644 --- a/sources/pyside-tools/deploy_lib/__init__.py +++ b/sources/pyside-tools/deploy_lib/__init__.py @@ -1,6 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only from __future__ import annotations +import logging import sys from pathlib import Path from textwrap import dedent @@ -44,6 +45,15 @@ HELP_EXTRA_MODULES = dedent(""" Example usage 2: --extra-modules=QtNetwork,QtSvg """) +HELP_MODE = dedent(""" + The mode in which the application is deployed. The options are: onefile, + standalone. The default value is onefile. + + This options translates to the mode Nuitka uses to create the executable. + + macOS by default uses the --standalone option. + """) + # plugins to be removed from the --include-qt-plugins option because these plugins # don't exist in site-package under PySide6/Qt/plugins PLUGINS_TO_REMOVE = ["accessiblebridge", "platforms/darwin", "networkaccess", @@ -60,9 +70,77 @@ def get_all_pyside_modules(): return [module[2:] for module in PySide6.__all__] -from .commands import run_command, run_qmlimportscanner -from .dependency_util import find_pyside_modules, find_permission_categories, QtDependencyReader -from .nuitka_helper import Nuitka -from .config import BaseConfig, Config, DesktopConfig -from .python_helper import PythonExecutable -from .deploy_util import cleanup, finalize, create_config_file, config_option_exists +def add_deploy_arguments(parser, include_main_file=True): + """Add pyside6-deploy arguments to an argparse parser. + + When include_main_file is False, the positional main_file argument is omitted + (used when called from pyside6-project where the main file comes from the project file). + """ + + parser.add_argument("-c", "--config-file", type=lambda p: Path(p).absolute(), + default=None, + help="Path to the .spec config file") + + if include_main_file: + parser.add_argument( + type=lambda p: Path(p).absolute(), + help="Path to main python file", nargs="?", dest="main_file", + default=None if config_option_exists() else Path.cwd() / "main.py") + + parser.add_argument( + "--init", action="store_true", + help="Create pysidedeploy.spec file, if it doesn't already exists") + + parser.add_argument( + "-v", "--verbose", help="Run in verbose mode", action="store_const", + dest="loglevel", const=logging.INFO) + + parser.add_argument("--dry-run", action="store_true", help="Show the commands to be run") + + parser.add_argument( + "--keep-deployment-files", action="store_true", + help="Keep the generated deployment files generated") + + parser.add_argument("-f", "--force", action="store_true", help="Force all input prompts") + + parser.add_argument("--name", type=str, help="Application name") + + parser.add_argument("--extra-ignore-dirs", type=str, help=HELP_EXTRA_IGNORE_DIRS) + + parser.add_argument("--extra-modules", type=str, help=HELP_EXTRA_MODULES) + + parser.add_argument("--mode", choices=["onefile", "standalone"], default=None, + help=HELP_MODE) + + +from .commands import ( # noqa: F401, E402 + run_command, + run_qmlimportscanner, +) + +from .dependency_util import ( # noqa: F401, E402 + find_pyside_modules, + find_permission_categories, + QtDependencyReader, +) + +from .nuitka_helper import ( # noqa: F401, E402 + Nuitka, +) + +from .config import ( # noqa: F401, E402 + BaseConfig, + Config, + DesktopConfig, +) + +from .python_helper import ( # noqa: F401, E402 + PythonExecutable, +) + +from .deploy_util import ( # noqa: F401, E402 + cleanup, + finalize, + create_config_file, + config_option_exists, +) diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py index 853f5f6a..3049c230 100644 --- a/sources/pyside-tools/deploy_lib/config.py +++ b/sources/pyside-tools/deploy_lib/config.py @@ -5,7 +5,7 @@ from __future__ import annotations import sys import configparser import logging -import tempfile +import io import warnings from configparser import ConfigParser from pathlib import Path @@ -45,26 +45,21 @@ class BaseConfig: def update_config(self): logging.info(f"[DEPLOY] Updating config file {self.config_file}") - # This section of code is done to preserve the formatting of the original deploy.spec - # file where there is blank line before the comments - with tempfile.NamedTemporaryFile('w+', delete=False) as temp_file: - self.parser.write(temp_file, space_around_delimiters=True) - temp_file_path = temp_file.name + # Write to an in-memory buffer first so we can post-process (insert blank lines + # before comments) without creating a temporary file on disk. + buffer = io.StringIO() + self.parser.write(buffer, space_around_delimiters=True) + buffer.seek(0) - # Read the temporary file and write back to the original file with blank lines before - # comments - with open(temp_file_path, 'r') as temp_file, open(self.config_file, 'w') as config_file: + with open(self.config_file, 'w') as config_file: previous_line = None - for line in temp_file: + for line in buffer: if (line.lstrip().startswith('#') and previous_line is not None and not previous_line.lstrip().startswith('#')): config_file.write('\n') config_file.write(line) previous_line = line - # Clean up the temporary file - Path(temp_file_path).unlink() - def set_value(self, section: str, key: str, new_value: str, raise_warning: bool = True) -> None: try: current_value = self.get_value(section, key, ignore_fail=True) @@ -129,7 +124,7 @@ class Config(BaseConfig): proj_dir = self.get_value("app", "project_dir") if proj_dir: - self._project_dir = Path(proj_dir).resolve() + self._project_dir = (self.config_file.parent / proj_dir).resolve() else: self.project_dir = self._find_project_dir() @@ -154,7 +149,7 @@ class Config(BaseConfig): self._qml_files = [] # Design Studio projects include the qml files using Qt resources - if source_file and not DesignStudioProject.is_ds_project(source_file): + if self.source_file and not DesignStudioProject.is_ds_project(self.source_file): config_qml_files = self.get_value("qt", "qml_files") if config_qml_files and self.project_dir and self.existing_config_file: self._qml_files = [Path(self.project_dir) @@ -439,8 +434,7 @@ class DesktopConfig(Config): if self.get_value("qt", "plugins"): self._qt_plugins = self.get_value("qt", "plugins").split(",") else: - self.qt_plugins = self.dependency_reader.find_plugin_dependencies(self.modules, - python_exe) + self.qt_plugins = self.dependency_reader.find_plugin_dependencies(self.modules) self._permissions = [] if sys.platform == "darwin": diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec index 5e33d539..e7e19338 100644 --- a/sources/pyside-tools/deploy_lib/default.spec +++ b/sources/pyside-tools/deploy_lib/default.spec @@ -24,7 +24,7 @@ icon = python_path = # Python packages to install -packages = Nuitka==2.7.11 +packages = Nuitka==4.1.1 # Buildozer: for deploying Android application android_packages = buildozer==1.5.0,cython==0.29.33 diff --git a/sources/pyside-tools/deploy_lib/dependency_util.py b/sources/pyside-tools/deploy_lib/dependency_util.py index 63b40060..9e331f15 100644 --- a/sources/pyside-tools/deploy_lib/dependency_util.py +++ b/sources/pyside-tools/deploy_lib/dependency_util.py @@ -13,6 +13,7 @@ import shutil import sys from pathlib import Path from functools import lru_cache +from importlib.metadata import distributions as _distributions from . import IMPORT_WARNING_PYSIDE, DEFAULT_IGNORE_DIRS, run_command @@ -202,7 +203,6 @@ class QtDependencyReader: self.lib_reader_name = None self.qt_module_path_pattern = None self.lib_pattern = None - self.command = None self.qt_libs_dir = None if sys.platform == "linux": @@ -303,16 +303,13 @@ class QtDependencyReader: else: logging.info(f"[DEPLOY] No Qt dependencies found for {module}") - def find_plugin_dependencies(self, used_modules: list[str], python_exe: Path) -> list[str]: + def find_plugin_dependencies(self, used_modules: list[str]) -> list[str]: """ Given the modules used by the application, returns all the required plugins """ plugins = set() pyside_wheels = ["PySide6_Essentials", "PySide6_Addons"] - # TODO from 3.12 use list(dist.name for dist in importlib.metadata.distributions()) - _, installed_packages = run_command(command=[str(python_exe), "-m", "pip", "freeze"], - dry_run=False, fetch_output=True) - installed_packages = [p.decode().split('==')[0] for p in installed_packages.split()] + installed_packages = {d.metadata['Name'] for d in _distributions() if d.metadata['Name']} for pyside_wheel in pyside_wheels: if pyside_wheel not in installed_packages: # the wheel is not installed and hence no plugins are checked for its modules diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py index bc2896a4..fbec0d14 100644 --- a/sources/pyside-tools/deploy_lib/nuitka_helper.py +++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py @@ -1,10 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only from __future__ import annotations - -# enables to use typehints for classes that has not been defined yet or imported -# used for resolving circular imports -from __future__ import annotations import logging import os import shlex diff --git a/sources/pyside-tools/deploy_lib/python_helper.py b/sources/pyside-tools/deploy_lib/python_helper.py index f234f937..0ad2d9df 100644 --- a/sources/pyside-tools/deploy_lib/python_helper.py +++ b/sources/pyside-tools/deploy_lib/python_helper.py @@ -7,7 +7,7 @@ import os import sys from importlib import util -from importlib.metadata import version +from importlib.metadata import distributions as _distributions, version from pathlib import Path from . import Config, run_command @@ -52,7 +52,7 @@ class PythonExecutable: @staticmethod def is_venv(): venv = os.environ.get("VIRTUAL_ENV") - return True if venv else False + return bool(venv) def is_pyenv_python(self): pyenv_root = os.environ.get("PYENV_ROOT") @@ -65,9 +65,8 @@ class PythonExecutable: return False def install(self, packages: list = None): - _, installed_packages = run_command(command=[str(self.exe), "-m", "pip", "freeze"], - dry_run=False, fetch_output=True) - installed_packages = [p.decode().split('==')[0] for p in installed_packages.split()] + installed_packages = {d.metadata['Name'].lower() for d in _distributions() + if d.metadata['Name']} for package in packages: package_info = package.split('==') package_components_len = len(package_info) @@ -79,7 +78,8 @@ class PythonExecutable: package_version = package_info[1] else: raise ValueError(f"{package} should be of the format 'package_name'=='version'") - if (package_name not in installed_packages) and (not self.is_installed(package_name)): + if (package_name.lower() not in installed_packages + and not self.is_installed(package_name)): logging.info(f"[DEPLOY] Installing package: {package}") run_command( command=[self.exe, "-m", "pip", "install", package], @@ -103,11 +103,29 @@ class PythonExecutable: def is_installed(self, package): return bool(util.find_spec(package)) - def install_dependencies(self, config: Config, packages: str, is_android: bool = False): + def install_dependencies(self, config: Config, packages: str, is_android: bool = False, + nuitka_version: str = None): """ Installs the python package dependencies for the target deployment platform """ - packages = config.get_value("python", packages).split(",") + packages_str = config.get_value("python", packages) + if not packages_str: + logging.warning(f"[DEPLOY] No packages found in config for '{packages}', skipping") + return + packages = packages_str.split(",") + if nuitka_version: + # Explicit version requested via cli. Install it. + packages = [ + f"Nuitka=={nuitka_version}" if p.strip().lower().startswith("nuitka") else p + for p in packages + ] + else: + # No explicit version: if Nuitka is already installed, use that. Otherwise, the one + # from pysidedeploy.spec + packages = [ + p.split("==")[0] if p.strip().lower().startswith("nuitka") else p + for p in packages + ] if not self.init: # install packages needed for deployment logging.info("[DEPLOY] Installing dependencies") diff --git a/sources/pyside-tools/metaobjectdump.py b/sources/pyside-tools/metaobjectdump.py index f3c0c560..cf90d5b7 100644 --- a/sources/pyside-tools/metaobjectdump.py +++ b/sources/pyside-tools/metaobjectdump.py @@ -8,6 +8,7 @@ import os import sys import tokenize from argparse import ArgumentParser, RawTextHelpFormatter +from enum import auto, Enum from pathlib import Path from typing import Union @@ -16,6 +17,13 @@ DESCRIPTION = """Parses Python source code to create QObject metatype information in JSON format for qmltyperegistrar.""" +class ClassType(Enum): + OTHER = auto() + QOBJECT = auto() + ENUM = auto() + FLAG = auto() + + REVISION = 68 @@ -39,11 +47,12 @@ ITEM_MODELS = ["QAbstractListModel", "QAbstractProxyModel", QOBJECT_DERIVED = ["QObject", "QQuickItem", "QQuickPaintedItem"] + ITEM_MODELS -# Python 3.9 does not support this syntax, yet -# AstDecorator = ast.Name | ast.Call -# AstPySideTypeSpec = ast.Name | ast.Constant -AstDecorator = Union[ast.Name, ast.Call] -AstPySideTypeSpec = Union[ast.Name, ast.Constant] +ENUM_TYPES = ["Enum", "IntEnum"] +FLAG_TYPES = ["Flag", "IntFlag"] + + +AstDecorator = ast.Name | ast.Call +AstPySideTypeSpec = ast.Name | ast.Constant ClassList = list[dict] @@ -136,6 +145,17 @@ def _parse_slot(func_name: str, call: ast.Call) -> Slot: "returnType": return_type} +def _parse_enum_values(enum_node: ast.ClassDef) -> list[str]: + """Return enum value names (assignments in the class body).""" + values = [] + for part in enum_node.body: + if isinstance(part, ast.Assign): + for target in part.targets: + if isinstance(target, ast.Name): + values.append(target.id) + return values + + class VisitorContext: """Stores a list of QObject-derived classes encountered in order to find out which classes inherit QObject.""" @@ -194,11 +214,47 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): elif var_name == QML_IMPORT_MINOR_VERSION: self._qml_import_minor_version = value - def visit_ClassDef(self, node: ast.Module): + def _parse_bases(self, node: ast.ClassDef) -> (ClassType, list): + """Parse the base classes and return the type and a JSON list.""" + class_type = ClassType.OTHER + bases = [] + for b in node.bases: + # PYSIDE-2202: catch weird constructs like "class C(type(Base)):" + if isinstance(b, ast.Name): + base_name = _name(b) + if base_name in self._context.qobject_derived: + class_type = ClassType.QOBJECT + self._context.qobject_derived.append(node.name) + elif base_name in ENUM_TYPES: + class_type = ClassType.ENUM + elif base_name in FLAG_TYPES: + class_type = ClassType.FLAG + bases.append({"access": "public", "name": base_name}) + return class_type, bases + + def _parse_enum_class(self, node: ast.ClassDef, is_flag): + decorator_name = "QFlag" if is_flag else "QEnum" + decorated = False + for d in node.decorator_list: + if isinstance(d, ast.Name): + if d.id == decorator_name: + decorated = True + break + if decorated: + enum_entry = {"name": node.name, "isClass": True, "isFlag": is_flag, + "type": "int", "values": _parse_enum_values(node)} + self._enums.append(enum_entry) + + def visit_ClassDef(self, node: ast.ClassDef): """Visit a class definition""" + class_type, bases = self._parse_bases(node) + if class_type == ClassType.ENUM or class_type == ClassType.FLAG: + self._parse_enum_class(node, class_type == ClassType.FLAG) + return self._properties = [] self._signals = [] self._slots = [] + self._enums = [] self._within_class = True qualified_name = node.name last_dot = qualified_name.rfind('.') @@ -206,21 +262,9 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): else qualified_name) data = {"className": name, - "qualifiedClassName": qualified_name} + "qualifiedClassName": qualified_name, + "object": class_type == ClassType.QOBJECT} - q_object = False - bases = [] - for b in node.bases: - # PYSIDE-2202: catch weird constructs like "class C(type(Base)):" - if isinstance(b, ast.Name): - base_name = _name(b) - if base_name in self._context.qobject_derived: - q_object = True - self._context.qobject_derived.append(name) - base_dict = {"access": "public", "name": base_name} - bases.append(base_dict) - - data["object"] = q_object if bases: data["superClasses"] = bases @@ -246,6 +290,9 @@ class MetaObjectDumpVisitor(ast.NodeVisitor): if self._slots: data["slots"] = self._slots + if self._enums: + data["enums"] = self._enums + self._json_class_list.append(data) self._within_class = False diff --git a/sources/pyside-tools/project.py b/sources/pyside-tools/project.py index edc8813a..8ced7ae6 100644 --- a/sources/pyside-tools/project.py +++ b/sources/pyside-tools/project.py @@ -12,6 +12,7 @@ from project_lib import (QmlProjectData, check_qml_decorators, is_python_file, m TRANSLATION_SUFFIX, requires_rebuild, run_command, remove_path, ProjectData, resolve_valid_project_file, new_project, NewProjectTypes, ClOptions, DesignStudioProject) +from deploy_lib import add_deploy_arguments DESCRIPTION = """ pyside6-project is a command line tool for creating, building and deploying Qt for Python @@ -104,13 +105,19 @@ class Project: """Return path and command for a file's artifact""" if file.suffix == ".ui": # Qt form files py_file = f"{file.parent}/ui_{file.stem}.py" - return [Path(py_file)], [UIC_CMD, os.fspath(file), "--rc-prefix", "-o", py_file] + cmd = [UIC_CMD] + cmd.extend(self.project.uic_options) + cmd.extend([os.fspath(file), "--rc-prefix", "-o", py_file]) + return [Path(py_file)], cmd if file.suffix == ".qrc": # Qt resources if not output_path: py_file = f"{file.parent}/rc_{file.stem}.py" else: py_file = str(output_path.resolve()) - return [Path(py_file)], [RCC_CMD, os.fspath(file), "-o", py_file] + cmd = [RCC_CMD] + cmd.extend(self.project.rcc_options) + cmd.extend([os.fspath(file), "-o", py_file]) + return [Path(py_file)], cmd # generate .qmltypes from sources with Qml decorators if file.suffix == ".py" and file in self._qml_module_sources: assert self._qml_module_dir @@ -238,10 +245,29 @@ class Project: Project(project_file=sub_project_file)._qmllint() self._qmllint() - def deploy(self): + def deploy(self, args: Namespace): """Deploys the application""" - cmd = [DEPLOY_CMD] - cmd.extend([str(self.project.main_file), "-f"]) + cmd = [DEPLOY_CMD, str(self.project.main_file)] + if args.config_file is not None: + cmd.extend(["-c", str(args.config_file)]) + if args.init: + cmd.append("--init") + if args.loglevel is not None: + cmd.append("--verbose") + if args.dry_run: + cmd.append("--dry-run") + if args.keep_deployment_files: + cmd.append("--keep-deployment-files") + if args.force: + cmd.append("--force") + if args.name: + cmd.extend(["--name", args.name]) + if args.extra_ignore_dirs: + cmd.extend(["--extra-ignore-dirs", args.extra_ignore_dirs]) + if args.extra_modules: + cmd.extend(["--extra-modules", args.extra_modules]) + if args.mode is not None: + cmd.extend(["--mode", args.mode]) run_command(cmd, cwd=self.project.project_file.parent) def lupdate(self): @@ -253,7 +279,7 @@ class Project: file=sys.stderr) return - source_files = self.project.python_files + self.project.ui_files + source_files = self.project.python_files + self.project.ui_files + self.project.qml_files project_dir = self.project.project_file.parent cmd_prefix = [LUPDATE_CMD] + [os.fspath(p.relative_to(project_dir)) for p in source_files] cmd_prefix.append("-ts") @@ -268,7 +294,7 @@ class Project: def main(args: Namespace): - mode = args.mode + mode = args.subcommand project_dir = getattr(args, "project_dir", None) project_path = getattr(args, "project_path", None) legacy_pyproject = getattr(args, "legacy_pyproject", False) @@ -310,7 +336,7 @@ def main(args: Namespace): elif mode == "qmllint": project.qmllint() elif mode == "deploy": - project.deploy() + project.deploy(args) elif mode == "lupdate": project.lupdate() else: @@ -327,7 +353,7 @@ if __name__ == "__main__": help="Perform check for QML module") # Create subparsers for the two different command branches - subparsers = parser.add_subparsers(dest='mode', required=True) + subparsers = parser.add_subparsers(dest='subcommand', required=True) # Add subparser for project creation commands for project_type in NewProjectTypes: @@ -345,5 +371,7 @@ if __name__ == "__main__": op_parser.add_argument("project_path", nargs="?", type=str, help="Path to the project file") if op_mode == "run": op_parser.add_argument('arguments', nargs='*', help="Arguments") + if op_mode == "deploy": + add_deploy_arguments(op_parser, include_main_file=False) main(parser.parse_args()) diff --git a/sources/pyside-tools/project_lib/project_data.py b/sources/pyside-tools/project_lib/project_data.py index 9a219c95..668e91e2 100644 --- a/sources/pyside-tools/project_lib/project_data.py +++ b/sources/pyside-tools/project_lib/project_data.py @@ -34,8 +34,10 @@ class ProjectData: self._python_files: list[Path] = [] # ui files self._ui_files: list[Path] = [] + self._uic_options: list[str] = [] # qrc files self._qrc_files: list[Path] = [] + self._rcc_options: list[str] = [] # ts files self._ts_files: list[Path] = [] @@ -53,6 +55,9 @@ class ProjectData: print(f"{error}", file=sys.stderr) sys.exit(1) + self._rcc_options = project_file_data.rcc_options + self._uic_options = project_file_data.uic_options + for f in project_file_data.files: file = Path(project_file.parent / f) if any(file.match(pattern) for pattern in PYPROJECT_FILE_PATTERNS): @@ -100,10 +105,18 @@ class ProjectData: def ui_files(self): return self._ui_files + @property + def uic_options(self): + return self._uic_options + @property def qrc_files(self): return self._qrc_files + @property + def rcc_options(self): + return self._rcc_options + @property def qml_files(self): return self._qml_files @@ -160,16 +173,13 @@ class QmlProjectData: meta_dir = qt_metatype_json_dir() for mod in self._qt_modules: mod_id = mod[2:].lower() - pattern = f"qt6{mod_id}_*" - if sys.platform != "win32": - pattern += "_" # qt6core_debug_metatypes.json (Linux) - pattern += METATYPES_JSON_SUFFIX + pattern = f"qt6{mod_id}_*{METATYPES_JSON_SUFFIX}" for f in meta_dir.glob(pattern): foreign_files.append(os.fspath(f)) break - if foreign_files: - foreign_files_str = ",".join(foreign_files) - result.append(f"--foreign-types={foreign_files_str}") + if foreign_files: + foreign_files_str = ",".join(foreign_files) + result.append(f"--foreign-types={foreign_files_str}") return result @property diff --git a/sources/pyside-tools/project_lib/pyproject_parse_result.py b/sources/pyside-tools/project_lib/pyproject_parse_result.py index 6a04bf5c..4c3264b5 100644 --- a/sources/pyside-tools/project_lib/pyproject_parse_result.py +++ b/sources/pyside-tools/project_lib/pyproject_parse_result.py @@ -8,3 +8,5 @@ from pathlib import Path class PyProjectParseResult: errors: list[str] = field(default_factory=list) files: list[Path] = field(default_factory=list) + rcc_options: list[str] = field(default_factory=list) + uic_options: list[str] = field(default_factory=list) diff --git a/sources/pyside-tools/project_lib/pyproject_toml.py b/sources/pyside-tools/project_lib/pyproject_toml.py index 6da7b455..c316cad7 100644 --- a/sources/pyside-tools/project_lib/pyproject_toml.py +++ b/sources/pyside-tools/project_lib/pyproject_toml.py @@ -4,51 +4,18 @@ from __future__ import annotations import os import sys -# TODO: Remove this import when Python 3.11 is the minimum supported version -if sys.version_info >= (3, 11): - import tomllib from pathlib import Path +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib # type: ignore[no-redef] + from . import PYPROJECT_JSON_PATTERN from .pyproject_parse_result import PyProjectParseResult from .pyproject_json import parse_pyproject_json -def _parse_toml_content(content: str) -> dict: - """ - Parse TOML content for project name and files list only. - """ - result = {"project": {}, "tool": {"pyside6-project": {}}} - current_section = None - - for line in content.splitlines(): - line = line.strip() - if not line or line.startswith('#'): - continue - - if line == '[project]': - current_section = 'project' - elif line == '[tool.pyside6-project]': - current_section = 'tool.pyside6-project' - elif '=' in line and current_section: - key, value = [part.strip() for part in line.split('=', 1)] - - # Handle string values - name of the project - if value.startswith('"') and value.endswith('"'): - value = value[1:-1] - # Handle array of strings - files names - elif value.startswith('[') and value.endswith(']'): - items = value[1:-1].split(',') - value = [item.strip().strip('"') for item in items if item.strip()] - - if current_section == 'project': - result['project'][key] = value - else: # tool.pyside6-project - result['tool']['pyside6-project'][key] = value - - return result - - def _write_base_toml_content(data: dict) -> str: """ Write minimal TOML content with project and tool.pyside6-project sections. @@ -81,21 +48,23 @@ def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult: try: content = pyproject_toml_file.read_text(encoding='utf-8') - # TODO: Remove the manual parsing when Python 3.11 is the minimum supported version - if sys.version_info >= (3, 11): - root_table = tomllib.loads(content) # Use tomllib for Python >= 3.11 - print("Using tomllib for parsing TOML content") - else: - root_table = _parse_toml_content(content) # Fallback to manual parsing + root_table = tomllib.loads(content) except Exception as e: result.errors.append(str(e)) return result - pyside_table = root_table.get("tool", {}).get("pyside6-project", {}) + tool_entry = root_table.get("tool", {}) + pyside_table = tool_entry.get("pyside6-project", {}) if not pyside_table: result.errors.append("Missing [tool.pyside6-project] table") return result + if rcc_table := tool_entry.get("pyside6-rcc", {}): + result.rcc_options = rcc_table.get("options", []) + + if uic_table := tool_entry.get("pyside6-uic", {}): + result.uic_options = uic_table.get("options", []) + files = pyside_table.get("files", []) if not isinstance(files, list): result.errors.append("Missing or invalid files list") diff --git a/sources/pyside-tools/pyside_tool.py b/sources/pyside-tools/pyside_tool.py index 5c009859..5530cdb1 100644 --- a/sources/pyside-tools/pyside_tool.py +++ b/sources/pyside-tools/pyside_tool.py @@ -5,9 +5,11 @@ from __future__ import annotations import importlib import os +import shutil import subprocess import sys import sysconfig +import tempfile from pathlib import Path import PySide6 as ref_mod @@ -15,17 +17,6 @@ import PySide6 as ref_mod VIRTUAL_ENV = "VIRTUAL_ENV" -def is_pyenv_python(): - pyenv_root = os.environ.get("PYENV_ROOT") - - if pyenv_root: - resolved_exe = Path(sys.executable).resolve() - if str(resolved_exe).startswith(pyenv_root): - return True - - return False - - def is_virtual_env(): return sys.prefix != sys.base_prefix @@ -138,6 +129,32 @@ def _extend_path_var(var, value, prepend=False): os.environ[var] = env_value +def _prepare_designer_app(app_path: Path) -> Path: + """Copy Designer.app to a temp dir, strip com.apple.provenance, re-sign + ad-hoc, and return the binary path inside the prepared copy. + + Only called when com.apple.provenance is detected on the app bundle, which + happens when Designer.app originates from an internet download (e.g. Qt + installed via the Qt Maintenance Tool). The provenance attribute causes the + codesign subsystem to lock _CodeSignature/*, preventing in-place re-signing + even by the owner. Copying to /tmp gives us unconditional ownership of the + inodes, so xattr and codesign always succeed there. + """ + temp_app = Path(tempfile.mkdtemp()) / app_path.name + shutil.copytree(app_path, temp_app) + # Strip provenance from the copy (succeeds because we own it). + subprocess.call(['xattr', '-dr', 'com.apple.provenance', os.fspath(temp_app)], + stderr=subprocess.DEVNULL) + # Re-sign ad-hoc to drop Qt's Library Validation entitlement, which would + # cause macOS to SIGKILL Designer when DYLD_INSERT_LIBRARIES injects + # libpython (not signed by Qt). Safe because Designer has no network access; + # the only remaining risk is a compromised libpython on disk, which is a + # general risk for any Python package and cannot be mitigated here. + subprocess.call(['codesign', '--force', '--deep', '--sign', '-', + os.fspath(temp_app)], stderr=subprocess.DEVNULL) + return temp_app / 'Contents' / 'MacOS' / app_path.stem + + def designer(): init_virtual_env() @@ -156,8 +173,11 @@ def designer(): # Determine library name (examples/utils/pyside_config.py) version = f'{major_version}.{minor_version}' library_name = f'libpython{version}{sys.abiflags}.so' - if is_pyenv_python(): - library_name = str(Path(sysconfig.get_config_var('LIBDIR')) / library_name) + # Non-system Pythons (pyenv, uv, etc.) are not on the default ld.so search path; + # resolve to an absolute path so LD_PRELOAD can find the library. + libdir = sysconfig.get_config_var('LIBDIR') + if libdir and Path(libdir, library_name).exists(): + library_name = str(Path(libdir) / library_name) os.environ['LD_PRELOAD'] = library_name elif sys.platform == 'darwin': library_name = sysconfig.get_config_var("LDLIBRARY") @@ -165,25 +185,52 @@ def designer(): lib_path = None if framework_prefix: lib_path = os.fspath(Path(framework_prefix) / library_name) - elif is_pyenv_python(): - lib_path = str(Path(sysconfig.get_config_var('LIBDIR')) / library_name) else: - # ideally this should never be reached because the system Python and Python installed - # from python.org are all framework builds + # Non-framework Pythons (pyenv, uv, Homebrew, conda, etc.) ship a dylib in LIBDIR. + libdir = sysconfig.get_config_var('LIBDIR') + if libdir and Path(libdir, library_name).exists(): + lib_path = str(Path(libdir) / library_name) + if not lib_path: print("Unable to find Python library directory. Use a framework build of Python.", file=sys.stderr) sys.exit(0) os.environ['DYLD_INSERT_LIBRARIES'] = lib_path + pyside_dir = Path(ref_mod.__file__).resolve().parent + _extend_path_var('DYLD_FRAMEWORK_PATH', os.fspath(pyside_dir / 'Qt' / 'lib')) + designer_app = pyside_dir / 'Designer.app' + # com.apple.provenance is only present on apps that originated from an + # internet download (e.g. Qt installed via the Qt Maintenance Tool). + # PyPI wheels are built from source in COIN and carry no provenance, so + # the copy-and-re-sign step is unnecessary. + # Note: this is not a CRA violation since for wheel case we do not do the copy-and-re-sign + # step at all + has_provenance = subprocess.call( + ['xattr', '-p', 'com.apple.provenance', os.fspath(designer_app)], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0 + if has_provenance: + # The temp copy's @rpath entries are relative and won't resolve to Qt + # frameworks that live in the original PySide6 directory. Point dyld + # at the real Qt lib directory so framework loading succeeds. + designer_binary = _prepare_designer_app(designer_app) + else: + designer_binary = designer_app / 'Contents' / 'MacOS' / designer_app.stem + cmd = [os.fspath(designer_binary), "--python-help"] + sys.argv[1:] + returncode = subprocess.call(cmd) + if returncode != 0: + print(f"'{' '.join(cmd)}' returned {returncode}", file=sys.stderr) + sys.exit(returncode) elif sys.platform == 'win32': # Find Python DLLs from the base installation if is_virtual_env(): _extend_path_var("PATH", os.fspath(Path(sys._base_executable).parent), True) - qt_tool_wrapper(ui_tool_binary("designer"), sys.argv[1:]) + args = ["--python-help"] + sys.argv[1:] + qt_tool_wrapper(ui_tool_binary("designer"), args) def linguist(): - qt_tool_wrapper(ui_tool_binary("linguist"), sys.argv[1:]) + args = ["--web-help"] + sys.argv[1:] + qt_tool_wrapper(ui_tool_binary("linguist"), args) def genpyi(): diff --git a/sources/pyside6/.cmake.conf b/sources/pyside6/.cmake.conf index 60d72e84..4cc79034 100644 --- a/sources/pyside6/.cmake.conf +++ b/sources/pyside6/.cmake.conf @@ -1,5 +1,5 @@ set(pyside_MAJOR_VERSION "6") -set(pyside_MINOR_VERSION "10") -set(pyside_MICRO_VERSION "3") +set(pyside_MINOR_VERSION "11") +set(pyside_MICRO_VERSION "2") set(pyside_PRE_RELEASE_VERSION_TYPE "") set(pyside_PRE_RELEASE_VERSION "") diff --git a/sources/pyside6/CMakeLists.txt b/sources/pyside6/CMakeLists.txt index 6b73d70a..bfe39569 100644 --- a/sources/pyside6/CMakeLists.txt +++ b/sources/pyside6/CMakeLists.txt @@ -3,8 +3,8 @@ include(cmake/Macros/icecc.cmake) # this must be the first line! -cmake_minimum_required(VERSION 3.18) -cmake_policy(VERSION 3.18) +cmake_minimum_required(VERSION 3.22) +cmake_policy(VERSION 3.22) include(".cmake.conf") project(pysidebindings) diff --git a/sources/pyside6/PySide6/QtAsyncio/__init__.py b/sources/pyside6/PySide6/QtAsyncio/__init__.py index d284e4f6..da8c51b2 100644 --- a/sources/pyside6/PySide6/QtAsyncio/__init__.py +++ b/sources/pyside6/PySide6/QtAsyncio/__init__.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# Qt-Security score:significant reason:default from __future__ import annotations from .events import ( diff --git a/sources/pyside6/PySide6/QtAsyncio/events.py b/sources/pyside6/PySide6/QtAsyncio/events.py index 6e208845..2aab9db4 100644 --- a/sources/pyside6/PySide6/QtAsyncio/events.py +++ b/sources/pyside6/PySide6/QtAsyncio/events.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# Qt-Security score:significant reason:default from __future__ import annotations from PySide6.QtCore import (QCoreApplication, QDateTime, QDeadlineTimer, diff --git a/sources/pyside6/PySide6/QtAsyncio/futures.py b/sources/pyside6/PySide6/QtAsyncio/futures.py index 6b441549..45943089 100644 --- a/sources/pyside6/PySide6/QtAsyncio/futures.py +++ b/sources/pyside6/PySide6/QtAsyncio/futures.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# Qt-Security score:significant reason:default from __future__ import annotations from . import events diff --git a/sources/pyside6/PySide6/QtAsyncio/tasks.py b/sources/pyside6/PySide6/QtAsyncio/tasks.py index c6cb3a10..049ac2b6 100644 --- a/sources/pyside6/PySide6/QtAsyncio/tasks.py +++ b/sources/pyside6/PySide6/QtAsyncio/tasks.py @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +# Qt-Security score:significant reason:default from __future__ import annotations from . import events diff --git a/sources/pyside6/PySide6/QtCanvasPainter/CMakeLists.txt b/sources/pyside6/PySide6/QtCanvasPainter/CMakeLists.txt new file mode 100644 index 00000000..e15df3fc --- /dev/null +++ b/sources/pyside6/PySide6/QtCanvasPainter/CMakeLists.txt @@ -0,0 +1,74 @@ +# Copyright (C) 2026 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(QtCanvasPainter) + +set (QtCanvasPainter_DROPPED_ENTRIES) + +set(QtCanvasPainter_SRC +${QtCanvasPainter_GEN_DIR}/qcanvasboxgradient_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasboxshadow_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasbrush_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasconicalgradient_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvascustombrush_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasgradient_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasgradientstop_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasgridpattern_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasimage_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasimagepattern_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvaslineargradient_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasoffscreencanvas_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvaspainter_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvaspainterfactory_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvaspainteritem_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvaspainteritemrenderer_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvaspath_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvaspainterwidget_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasradialgradient_wrapper.cpp +${QtCanvasPainter_GEN_DIR}/qcanvasrhipaintdriver_wrapper.cpp +# module is always needed +${QtCanvasPainter_GEN_DIR}/qtcanvaspainter_module_wrapper.cpp +) + +set(QtGui_private_include_dirs + ${Qt${QT_MAJOR_VERSION}Core_PRIVATE_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}) + +configure_file("${QtCanvasPainter_SOURCE_DIR}/QtCanvasPainter_global.post.h.in" + "${QtCanvasPainter_BINARY_DIR}/QtCanvasPainter_global.post.h" @ONLY) + +set(QtCanvasPainter_include_dirs ${QtCanvasPainter_SOURCE_DIR} + ${QtQml_SOURCE_DIR} + ${Qt${QT_MAJOR_VERSION}Core_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Gui_INCLUDE_DIRS} + ${QtGui_private_include_dirs} + ${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Qml_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}Quick_INCLUDE_DIRS} + ${Qt${QT_MAJOR_VERSION}CanvasPainter_INCLUDE_DIRS} + ${libpyside_SOURCE_DIR} + ${QtWidgets_GEN_DIR} + ${QtGui_GEN_DIR} + ${QtCore_GEN_DIR} + ${QtNetwork_GEN_DIR} + ${QtQml_GEN_DIR} + ${QtQuick_GEN_DIR} + ${QtCanvasPainter_GEN_DIR}) + +set(QtCanvasPainter_libraries pyside6 + ${Qt${QT_MAJOR_VERSION}CanvasPainter_LIBRARIES}) + +set(QtCanvasPainter_deps QtGui QtWidgets QtNetwork QtQml QtQuick) + +check_qt_opengl("CanvasPainter" QtCanvasPainter_include_dirs QtCanvasPainter_deps + QtCanvasPainter_DROPPED_ENTRIES) + +create_pyside_module(NAME QtCanvasPainter + INCLUDE_DIRS QtCanvasPainter_include_dirs + LIBRARIES QtCanvasPainter_libraries + DEPS QtCanvasPainter_deps + TYPESYSTEM_PATH QtCanvasPainter_SOURCE_DIR + SOURCES QtCanvasPainter_SRC + DROPPED_ENTRIES QtCanvasPainter_DROPPED_ENTRIES + ADDITIONAL_INCLUDE_DIRS QtGui_private_include_dirs) diff --git a/sources/pyside6/PySide6/QtCanvasPainter/QtCanvasPainter_global.post.h.in b/sources/pyside6/PySide6/QtCanvasPainter/QtCanvasPainter_global.post.h.in new file mode 100644 index 00000000..9866da78 --- /dev/null +++ b/sources/pyside6/PySide6/QtCanvasPainter/QtCanvasPainter_global.post.h.in @@ -0,0 +1,3 @@ +#include // Some virtuals of QCPainterWidget and others use QRhi +#include +#include diff --git a/sources/pyside6/PySide6/QtCanvasPainter/typesystem_canvaspainter.xml b/sources/pyside6/PySide6/QtCanvasPainter/typesystem_canvaspainter.xml new file mode 100644 index 00000000..801fc41f --- /dev/null +++ b/sources/pyside6/PySide6/QtCanvasPainter/typesystem_canvaspainter.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtCore/CMakeLists.txt b/sources/pyside6/PySide6/QtCore/CMakeLists.txt index 2f49b610..997b0fe0 100644 --- a/sources/pyside6/PySide6/QtCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtCore/CMakeLists.txt @@ -32,6 +32,7 @@ ${QtCore_GEN_DIR}/qabstractproxymodel_wrapper.cpp ${QtCore_GEN_DIR}/qabstracttablemodel_wrapper.cpp ${QtCore_GEN_DIR}/qanimationgroup_wrapper.cpp ${QtCore_GEN_DIR}/qbasicmutex_wrapper.cpp +${QtCore_GEN_DIR}/qbasicreadwritelock_wrapper.cpp ${QtCore_GEN_DIR}/qbasictimer_wrapper.cpp ${QtCore_GEN_DIR}/qbitarray_wrapper.cpp ${QtCore_GEN_DIR}/qbuffer_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp index a77a8dde..c4b61a7b 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "core_snippets_p.h" #include "qtcorehelper.h" @@ -13,6 +14,7 @@ #endif #include "basewrapper.h" #include "autodecref.h" +#include "gilstate.h" #include "pysideutils.h" #include @@ -139,7 +141,7 @@ QString qObjectTr(PyTypeObject *type, const char *sourceText, const char *disamb auto len = PyTuple_Size(mro); QString result = QString::fromUtf8(sourceText); QString oldResult = result; - static auto *sbkObjectType = reinterpret_cast(SbkObject_TypeF()); + auto *sbkObjectType = reinterpret_cast(SbkObject_TypeF()); for (Py_ssize_t idx = 0; idx < len - 1; ++idx) { // Skip the last class which is `object`. auto *type = reinterpret_cast(PyTuple_GetItem(mro, idx)); diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h b/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h index 4c1867a1..f2f4436a 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets_p.h @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef CORE_SNIPPETS_P_H #define CORE_SNIPPETS_P_H diff --git a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp index 3ff157e4..704d032a 100644 --- a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.cpp @@ -1,7 +1,9 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include +#include #include #include #include diff --git a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h index 181a205f..717cc1d7 100644 --- a/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h +++ b/sources/pyside6/PySide6/QtCore/glue/qeasingcurve_glue.h @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef __QEASINGCURVE_GLUE__ #define __QEASINGCURVE_GLUE__ diff --git a/sources/pyside6/PySide6/QtCore/glue/qiopipe.cpp b/sources/pyside6/PySide6/QtCore/glue/qiopipe.cpp index 6799c0f3..e78b5838 100644 --- a/sources/pyside6/PySide6/QtCore/glue/qiopipe.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/qiopipe.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:critical reason:handling-untrusted-data #include "qiopipe.h" diff --git a/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp b/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp index a56417ac..3238be30 100644 --- a/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/qtcorehelper.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include diff --git a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml index 3be80d31..36a8ef37 100644 --- a/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml +++ b/sources/pyside6/PySide6/QtCore/typesystem_core_common.xml @@ -77,6 +77,8 @@ snippet="qarg_helper"/> + @@ -738,6 +740,8 @@ snippet="qlatin1string-check"/> + + @@ -1616,11 +1620,13 @@ + + + + - - @@ -1753,6 +1759,7 @@ + - - + + @@ -1830,6 +1837,8 @@ + @@ -1859,7 +1868,7 @@ - + @@ -2097,17 +2106,14 @@ - - - - - - - - - - - + + + + + + + + @@ -2186,12 +2192,6 @@ - - - - - - @@ -3178,8 +3178,8 @@ - - + + @@ -3536,6 +3536,11 @@ + + + + diff --git a/sources/pyside6/PySide6/QtDataVisualization/qtdatavisualization_helper.cpp b/sources/pyside6/PySide6/QtDataVisualization/qtdatavisualization_helper.cpp index 8ad31a77..6b2a0dd6 100644 --- a/sources/pyside6/PySide6/QtDataVisualization/qtdatavisualization_helper.cpp +++ b/sources/pyside6/PySide6/QtDataVisualization/qtdatavisualization_helper.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "qtdatavisualization_helper.h" diff --git a/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp b/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp index d93c038b..0d39b0e8 100644 --- a/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp +++ b/sources/pyside6/PySide6/QtDesigner/qpydesignercustomwidgetcollection.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include @@ -7,6 +8,7 @@ #include #include +#include #include #include #include @@ -17,8 +19,9 @@ QT_BEGIN_NAMESPACE static QString pyStringToQString(PyObject *s) { - const char *utf8 = _PepUnicode_AsString(s); - return utf8 ? QString::fromUtf8(utf8) : QString(); + Py_ssize_t size{}; + const char *utf8 = PyUnicode_AsUTF8AndSize(s, &size); + return utf8 ? QString::fromUtf8(utf8, size) : QString{}; } // Return a string from keyword argument dict @@ -49,9 +52,12 @@ class PyDesignerCustomWidget : public QDesignerCustomWidgetInterface { public: explicit PyDesignerCustomWidget(PyObject *pyTypeObject) : - m_pyTypeObject(pyTypeObject) {} + m_name(QString::fromUtf8(PepExt_TypeGetQualName(reinterpret_cast(pyTypeObject)))), + m_pyTypeObject(pyTypeObject) + { + } - QString name() const override; + QString name() const override { return m_name; } QString group() const override { return m_group; } QString toolTip() const override { return m_toolTip; } QString whatsThis() const override { return toolTip(); } @@ -74,9 +80,8 @@ public: void setContainer(bool container) { m_container = container; } private: - const char *utf8Name() const; - QDesignerFormEditorInterface *m_core = nullptr; + QString m_name; QString m_group; QString m_toolTip; QString m_includeFile; @@ -86,16 +91,6 @@ private: bool m_container = false; }; -const char *PyDesignerCustomWidget::utf8Name() const -{ - return reinterpret_cast(m_pyTypeObject)->tp_name; -} - -QString PyDesignerCustomWidget::name() const -{ - return QString::fromUtf8(utf8Name()); -} - QWidget *PyDesignerCustomWidget::createWidget(QWidget *parent) { // This is a copy of the similar function used for QUiLoader @@ -123,7 +118,7 @@ QWidget *PyDesignerCustomWidget::createWidget(QWidget *parent) // Call python constructor auto *obResult = PyObject_CallObject(m_pyTypeObject, pyArgs); if (obResult == nullptr) { - qWarning("Unable to create a Python custom widget of type \"%s\".", utf8Name()); + qWarning("Unable to create a Python custom widget of type \"%s\".", qPrintable(m_name)); PyErr_Print(); return nullptr; } diff --git a/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt b/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt index f4160787..1846f59e 100644 --- a/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGraphs/CMakeLists.txt @@ -23,6 +23,7 @@ ${QtGraphs_GEN_DIR}/qabstractdataproxy_wrapper.cpp ${QtGraphs_GEN_DIR}/qbar3dseries_wrapper.cpp ${QtGraphs_GEN_DIR}/qbardataitem_wrapper.cpp ${QtGraphs_GEN_DIR}/qbardataproxy_wrapper.cpp +${QtGraphs_GEN_DIR}/qcustomseries_wrapper.cpp ${QtGraphs_GEN_DIR}/qcustom3ditem_wrapper.cpp ${QtGraphs_GEN_DIR}/qcustom3dlabel_wrapper.cpp ${QtGraphs_GEN_DIR}/qcustom3dvolume_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp b/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp index 19fe4f81..d29a763b 100644 --- a/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp +++ b/sources/pyside6/PySide6/QtGraphs/qtgraphs_helper.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "qtgraphs_helper.h" diff --git a/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml b/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml index ba0a8a48..6cc0f3f5 100644 --- a/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml +++ b/sources/pyside6/PySide6/QtGraphs/typesystem_graphs.xml @@ -73,6 +73,7 @@ + @@ -151,7 +152,9 @@ - + + + @@ -181,7 +184,10 @@ - + + + + diff --git a/sources/pyside6/PySide6/QtGui/CMakeLists.txt b/sources/pyside6/PySide6/QtGui/CMakeLists.txt index b9e84291..778b1d64 100644 --- a/sources/pyside6/PySide6/QtGui/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtGui/CMakeLists.txt @@ -213,6 +213,7 @@ ${QtGui_GEN_DIR}/qpolygon_wrapper.cpp ${QtGui_GEN_DIR}/qpolygonf_wrapper.cpp ${QtGui_GEN_DIR}/qpytextobject_wrapper.cpp ${QtGui_GEN_DIR}/qquaternion_wrapper.cpp +${QtGui_GEN_DIR}/qquaternion_axes_wrapper.cpp ${QtGui_GEN_DIR}/qradialgradient_wrapper.cpp ${QtGui_GEN_DIR}/qregularexpressionvalidator_wrapper.cpp ${QtGui_GEN_DIR}/qrgba64_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml index ae298a8f..9892af70 100644 --- a/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml +++ b/sources/pyside6/PySide6/QtGui/typesystem_gui_common.xml @@ -133,7 +133,13 @@ - + + + + + + + + + + @@ -270,6 +279,7 @@ + @@ -464,8 +474,8 @@ - - + + @@ -494,8 +504,8 @@ - - + + @@ -1646,6 +1656,11 @@ + + + @@ -2619,6 +2634,7 @@ _______ end of matrix block _______ --> + @@ -2891,6 +2907,7 @@ + diff --git a/sources/pyside6/PySide6/QtOpenGL/glue/utils.cpp b/sources/pyside6/PySide6/QtOpenGL/glue/utils.cpp index 27a8a9fd..7ce90125 100644 --- a/sources/pyside6/PySide6/QtOpenGL/glue/utils.cpp +++ b/sources/pyside6/PySide6/QtOpenGL/glue/utils.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include diff --git a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp index d5c2e920..82b02b03 100644 --- a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp +++ b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.cpp @@ -1,8 +1,10 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "pysideqmlvolatilebool.h" +#include #include #include #include diff --git a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.h b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.h index fd3707ab..fed6192e 100644 --- a/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.h +++ b/sources/pyside6/PySide6/QtQml/pysideqmlvolatilebool.h @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef PYSIDEQMLVOLATILEBOOL_H #define PYSIDEQMLVOLATILEBOOL_H diff --git a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml index 96bb81ed..a6be58c5 100644 --- a/sources/pyside6/PySide6/QtQml/typesystem_qml.xml +++ b/sources/pyside6/PySide6/QtQml/typesystem_qml.xml @@ -99,14 +99,20 @@ + + + + + + @@ -193,6 +199,31 @@ + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp b/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp index e92c06a4..c751c722 100644 --- a/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp +++ b/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "pysidequickregistertype.h" diff --git a/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.h b/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.h index 89e0ced6..d7d119f2 100644 --- a/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.h +++ b/sources/pyside6/PySide6/QtQuick/pysidequickregistertype.h @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef PYSIDE_QUICK_REGISTER_TYPE_H #define PYSIDE_QUICK_REGISTER_TYPE_H diff --git a/sources/pyside6/PySide6/QtUiTools/glue/plugins.h b/sources/pyside6/PySide6/QtUiTools/glue/plugins.h index 04ec149f..5152695c 100644 --- a/sources/pyside6/PySide6/QtUiTools/glue/plugins.h +++ b/sources/pyside6/PySide6/QtUiTools/glue/plugins.h @@ -1,5 +1,6 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef _PLUGIN_H_ #define _PLUGIN_H_ diff --git a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt index 4d934438..c845e8c1 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineCore/CMakeLists.txt @@ -10,6 +10,8 @@ set(QtWebEngineCore_static_sources "${QtWebEngineCore_SOURCE_DIR}/glue/webengineframe.h" ) +set(QtWebEngineCore_DROPPED_ENTRIES) + set(QtWebEngineCore_SRC ${QtWebEngineCore_GEN_DIR}/qwebenginecertificateerror_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineclientcertificateselection_wrapper.cpp @@ -20,8 +22,6 @@ ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginecookiestore_filterrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginedesktopmediarequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginedownloadrequest_wrapper.cpp -${QtWebEngineCore_GEN_DIR}/qwebengineextensioninfo_wrapper.cpp -${QtWebEngineCore_GEN_DIR}/qwebengineextensionmanager_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefilesystemaccessrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebenginefindtextresult_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qwebengineframe_wrapper.cpp @@ -56,6 +56,17 @@ ${QtWebEngineCore_GEN_DIR}/qwebenginewebauthuxrequest_wrapper.cpp ${QtWebEngineCore_GEN_DIR}/qtwebenginecore_module_wrapper.cpp ) +get_property(QtWebEngineCore_enabled_features TARGET Qt${QT_MAJOR_VERSION}::WebEngineCore + PROPERTY QT_ENABLED_PUBLIC_FEATURES) + +if("webengine_extensions" IN_LIST QtWebEngineCore_enabled_features) + list(APPEND QtWebEngineCore_SRC + ${QtWebEngineCore_GEN_DIR}/qwebengineextensioninfo_wrapper.cpp + ${QtWebEngineCore_GEN_DIR}/qwebengineextensionmanager_wrapper.cpp) +else() + list(APPEND QtWebEngineCore_DROPPED_ENTRIES QWebEngineExtensionInfo QWebEngineExtensionManager) +endif() + set(QtWebEngineCore_include_dirs ${QtWebEngineCore_SOURCE_DIR} ${QtWebEngineCore_BINARY_DIR} @@ -84,4 +95,5 @@ create_pyside_module(NAME QtWebEngineCore LIBRARIES QtWebEngineCore_libraries DEPS QtWebEngineCore_deps TYPESYSTEM_PATH QtWebEngineCore_SOURCE_DIR - SOURCES QtWebEngineCore_SRC) + SOURCES QtWebEngineCore_SRC + DROPPED_ENTRIES QtWebEngineCore_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp index fb209d51..58a633ee 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "webengineframe.h" diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h index a792c7ba..73917934 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webengineframe.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef WEBENGINEFRAME_H #define WEBENGINEFRAME_H diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp index 3591591a..93792462 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "webenginepage_functors.h" @@ -7,7 +8,7 @@ #include "gilstate.h" #include "sbkconverter.h" -#include "pysideutils.h" +#include "pysidevariantutils.h" #include #include @@ -17,27 +18,7 @@ QT_BEGIN_NAMESPACE void RunJavascriptFunctor::operator()(const QVariant &result) { Shiboken::GilState state; - Shiboken::AutoDecRef arglist(PyTuple_New(1)); - switch (result.typeId()) { - case QMetaType::Bool: { - PyObject *pyValue = result.toBool() ? Py_True : Py_False; - Py_INCREF(pyValue); - PyTuple_SetItem(arglist, 0, pyValue); - } - break; - case QMetaType::Int: - case QMetaType::UInt: - case QMetaType::LongLong: - case QMetaType::ULongLong: - case QMetaType::Double: - PyTuple_SetItem(arglist, 0, PyFloat_FromDouble(result.toDouble())); - break; - default: { - const QString value = result.toString(); - PyTuple_SetItem(arglist, 0, PySide::qStringToPyUnicode(value)); - } - break; - } + Shiboken::AutoDecRef arglist(PyTuple_Pack(1, PySide::Variant::javascriptVariantToPython(result))); Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); release(); // single shot } diff --git a/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h index 535c2792..7cd48797 100644 --- a/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h +++ b/sources/pyside6/PySide6/QtWebEngineCore/glue/webenginepage_functors.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef WEBENGINEPAGE_FUNCTORS_H #define WEBENGINEPAGE_FUNCTORS_H diff --git a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt index 77152e9f..10383fbb 100644 --- a/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebEngineQuick/CMakeLists.txt @@ -11,6 +11,8 @@ ${QtWebEngineQuick_GEN_DIR}/qtwebenginequick_wrapper.cpp ${QtWebEngineQuick_GEN_DIR}/qtwebenginequick_module_wrapper.cpp ) +set(QtWebEngineQuick_DROPPED_ENTRIES) + set(QtWebEngineQuick_include_dirs ${QtWebEngineQuick_SOURCE_DIR} ${QtWebEngineQuick_BINARY_DIR} @@ -31,6 +33,15 @@ set(QtWebEngineQuick_include_dirs ${QtQml_GEN_DIR} ${QtWebChannel_GEN_DIR}) + +# Disable "QWebEngineExtensionManager *QQuickWebEngineProfile::extensionManager()" +get_property(QtWebEngineCore_enabled_features TARGET Qt${QT_MAJOR_VERSION}::WebEngineCore + PROPERTY QT_ENABLED_PUBLIC_FEATURES) + +if(NOT "webengine_extensions" IN_LIST QtWebEngineCore_enabled_features) + list(APPEND QtWebEngineQuick_DROPPED_ENTRIES QWebEngineExtensionInfo QWebEngineExtensionManager) +endif() + set(QtWebEngineQuick_libraries pyside6 ${Qt${QT_MAJOR_VERSION}WebEngineQuick_LIBRARIES}) @@ -41,4 +52,5 @@ create_pyside_module(NAME QtWebEngineQuick LIBRARIES QtWebEngineQuick_libraries DEPS QtWebEngineQuick_deps TYPESYSTEM_PATH QtWebEngineQuick_SOURCE_DIR - SOURCES QtWebEngineQuick_SRC) + SOURCES QtWebEngineQuick_SRC + DROPPED_ENTRIES QtWebEngineQuick_DROPPED_ENTRIES) diff --git a/sources/pyside6/PySide6/QtWebView/CMakeLists.txt b/sources/pyside6/PySide6/QtWebView/CMakeLists.txt index a8ac8c2a..4251bac4 100644 --- a/sources/pyside6/PySide6/QtWebView/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWebView/CMakeLists.txt @@ -5,6 +5,9 @@ project(QtWebView) set(QtWebView_Src ${QtWebView_GEN_DIR}/qtwebview_wrapper.cpp +${QtWebView_GEN_DIR}/qwebview_wrapper.cpp +${QtWebView_GEN_DIR}/qwebviewloadinginfo_wrapper.cpp +${QtWebView_GEN_DIR}/qwebviewsettings_wrapper.cpp # module is always needed ${QtWebView_GEN_DIR}/qtwebview_module_wrapper.cpp ) diff --git a/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml b/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml index 1983ed81..206fefa6 100644 --- a/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml +++ b/sources/pyside6/PySide6/QtWebView/typesystem_webview.xml @@ -5,6 +5,28 @@ --> - + + + + + + + + + + + + + + + + + + + + + diff --git a/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt b/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt index 1a77181f..e1b1332e 100644 --- a/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt +++ b/sources/pyside6/PySide6/QtWidgets/CMakeLists.txt @@ -151,6 +151,7 @@ ${QtWidgets_GEN_DIR}/qstyleoptiongroupbox_wrapper.cpp ${QtWidgets_GEN_DIR}/qstyleoptionheader_wrapper.cpp ${QtWidgets_GEN_DIR}/qstyleoptionheaderv2_wrapper.cpp ${QtWidgets_GEN_DIR}/qstyleoptionmenuitem_wrapper.cpp +${QtWidgets_GEN_DIR}/qstyleoptionmenuitemv2_wrapper.cpp ${QtWidgets_GEN_DIR}/qstyleoptionprogressbar_wrapper.cpp ${QtWidgets_GEN_DIR}/qstyleoptionrubberband_wrapper.cpp ${QtWidgets_GEN_DIR}/qstyleoptionsizegrip_wrapper.cpp diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index 8058c752..d5e44670 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -118,6 +118,11 @@ + + + @@ -239,7 +244,7 @@ - + @@ -862,7 +867,8 @@ - + + @@ -1232,7 +1238,7 @@ - + @@ -2231,6 +2237,7 @@ + @@ -2245,6 +2252,7 @@ + @@ -2259,6 +2267,7 @@ + @@ -2273,6 +2282,7 @@ + @@ -2386,7 +2396,7 @@ - + diff --git a/sources/pyside6/PySide6/doc/qtcanvaspainter.rst b/sources/pyside6/PySide6/doc/qtcanvaspainter.rst new file mode 100644 index 00000000..10fc08ac --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtcanvaspainter.rst @@ -0,0 +1,6 @@ +// @snippet qcpainterwidget-grabcanvas +Issues a texture readback request for ``canvas``. ``callback`` is invoked +either before the function returns, or later, depending on the underlying +``QRhi`` and 3D API implementation. Reading back texture contents may +involve a GPU->CPU copy, depending on the GPU architecture. +// @snippet qcpainterwidget-grabcanvas diff --git a/sources/pyside6/PySide6/doc/qtcore.rst b/sources/pyside6/PySide6/doc/qtcore.rst index eb369ee7..412c8784 100644 --- a/sources/pyside6/PySide6/doc/qtcore.rst +++ b/sources/pyside6/PySide6/doc/qtcore.rst @@ -94,6 +94,10 @@ to a list. // @snippet qsettings-value +// @snippet qjsonarray-toobjectlist +Returns a list of JSON objects ready for use in a QML ListView. +// @snippet qjsonarray-toobjectlist + // @snippet qmessagelogger In Python, the :class:`QMessageLogger` is useful to connect an existing logging diff --git a/sources/pyside6/PySide6/doc/qtgui.rst b/sources/pyside6/PySide6/doc/qtgui.rst new file mode 100644 index 00000000..268f9f66 --- /dev/null +++ b/sources/pyside6/PySide6/doc/qtgui.rst @@ -0,0 +1,4 @@ +// @snippet qstandarditemmodel-invisiblerootitem +The item returned by this function should not be used for populating the model +or removing items; this can lead to memory leaks. +// @snippet qstandarditemmodel-invisiblerootitem diff --git a/sources/pyside6/PySide6/doc/qtqml.rst b/sources/pyside6/PySide6/doc/qtqml.rst index ef171f80..53b25edf 100644 --- a/sources/pyside6/PySide6/doc/qtqml.rst +++ b/sources/pyside6/PySide6/doc/qtqml.rst @@ -150,3 +150,17 @@ to do a one time setup of a singleton; if repeated access to the singleton is required, caching its typeId will allow faster subsequent access via the type-id based overload. // @snippet qqmlengine-singletoninstance-typename + +// @snippet qqmlcomponent-create_withownership +Like :meth:`~PySide6.QtQml.QQmlComponent.create`, but the returned +``QObject`` is owned by Python rather than C++. This removes the need to +manually keep the object alive when the component is created from Python +but controlled natively (like in QtBridges). +// @snippet qqmlcomponent-create_withownership + +// @snippet qqmlcomponent-createwithinitialproperties_withownership +Like :meth:`~PySide6.QtQml.QQmlComponent.createWithInitialProperties`, but +the returned ``QObject`` is owned by Python rather than C++. This removes +the need to manually keep the object alive when the component is created from +Python but controlled natively (like in QtBridges). +// @snippet qqmlcomponent-createwithinitialproperties_withownership diff --git a/sources/pyside6/PySide6/doc/qtuitools.rst b/sources/pyside6/PySide6/doc/qtuitools.rst index a8856f1a..825fc4db 100644 --- a/sources/pyside6/PySide6/doc/qtuitools.rst +++ b/sources/pyside6/PySide6/doc/qtuitools.rst @@ -65,4 +65,17 @@ A simple use case is:: form.setupUi(widget) # form.a_widget_member.a_method_of_member() widget.show() + + +Security Considerations ++++++++++++++++++++++++ + +We strongly advise against using this function in security-critical environments. + +The function runs the external tool :ref:`pyside6-uic` to convert the ``.ui`` +files into Python code, which is then executed to create the return types. + +Manipulation of the executable or loading ``.ui`` files from untrusted sources +can lead to security threats in your application, such as denial of service +attacks, UI deception, or the loading of unexpected plugins. // @snippet loaduitype diff --git a/sources/pyside6/PySide6/glue/qhttpserver.cpp b/sources/pyside6/PySide6/glue/qhttpserver.cpp index dd08a38a..08a87e35 100644 --- a/sources/pyside6/PySide6/glue/qhttpserver.cpp +++ b/sources/pyside6/PySide6/glue/qhttpserver.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qtcanvaspainter.cpp b/sources/pyside6/PySide6/glue/qtcanvaspainter.cpp new file mode 100644 index 00000000..e09f84ce --- /dev/null +++ b/sources/pyside6/PySide6/glue/qtcanvaspainter.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +// @snippet qcpainterwidget-grabcanvas +auto callable = %PYARG_2; +auto callback = [callable](const QImage &result) +{ + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_New(1)); + PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QImage](result)); + Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); + Py_DECREF(callable); + +}; +Py_INCREF(callable); +%CPPSELF.%FUNCTION_NAME(%1, callback); +// @snippet qcpainterwidget-grabcanvas diff --git a/sources/pyside6/PySide6/glue/qtcharts.cpp b/sources/pyside6/PySide6/glue/qtcharts.cpp index a99dbbb8..0e1b6f62 100644 --- a/sources/pyside6/PySide6/glue/qtcharts.cpp +++ b/sources/pyside6/PySide6/glue/qtcharts.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qchart-releaseownership Shiboken::Object::releaseOwnership(%PYARG_1); diff --git a/sources/pyside6/PySide6/glue/qtcore.cpp b/sources/pyside6/PySide6/glue/qtcore.cpp index 8e206109..b6a64617 100644 --- a/sources/pyside6/PySide6/glue/qtcore.cpp +++ b/sources/pyside6/PySide6/glue/qtcore.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE @@ -15,6 +16,10 @@ #include "glue/core_snippets_p.h" // @snippet core-snippets-p-h +// @snippet libpyside-set-qobject-type +PySide::setQObjectType(pyType); +// @snippet libpyside-set-qobject-type + // @snippet qarg_helper // Helper for the Q_ARG/Q_RETURN_ARG functions, creating a meta type @@ -398,6 +403,8 @@ static void msgHandlerCallback(QtMsgType type, const QMessageLogContext &ctx, co const char *data = array.constData(); PyTuple_SetItem(arglist, 2, %CONVERTTOPYTHON[const char *](data)); Shiboken::AutoDecRef ret(PyObject_CallObject(qtmsghandler, arglist)); + if (Shiboken::Errors::occurred()) + Shiboken::Errors::storeErrorOrPrint(); } // @snippet qt-messagehandler @@ -739,7 +746,8 @@ if (step != 1 && value_length != slicelength) { if (step != 1) { Py_ssize_t i = start; for (Py_ssize_t j = 0; j < slicelength; ++j) { - PyObject *item = PyObject_GetItem(_value, PyLong_FromSsize_t(j)); + Shiboken::AutoDecRef key(PyLong_FromSsize_t(j)); + Shiboken::AutoDecRef item(PyObject_GetItem(_value, key)); QByteArray temp; if (PyLong_Check(item)) { int overflow; @@ -773,7 +781,11 @@ static int SbkQByteArray_getbufferproc(PyObject *obj, Py_buffer *view, int flags QByteArray * cppSelf = %CONVERTTOCPP[QByteArray *](obj); //XXX /|\ omitting this space crashes shiboken! -#ifdef Py_LIMITED_API + +#if !defined(Py_LIMITED_API) || Py_LIMITED_API >= 0x030B0000 + return PyBuffer_FillInfo(view, obj, reinterpret_cast(cppSelf->data()), + cppSelf->size(), 0, flags); +#else view->obj = obj; view->buf = reinterpret_cast(cppSelf->data()); view->len = cppSelf->size(); @@ -788,21 +800,15 @@ static int SbkQByteArray_getbufferproc(PyObject *obj, Py_buffer *view, int flags Py_XINCREF(obj); return 0; -#else // Py_LIMITED_API - const int result = PyBuffer_FillInfo(view, obj, reinterpret_cast(cppSelf->data()), - cppSelf->size(), 0, flags); - if (result == 0) - Py_XINCREF(obj); - return result; #endif } static PyBufferProcs SbkQByteArrayBufferProc = { - /*bf_getbuffer*/ (getbufferproc)SbkQByteArray_getbufferproc, - /*bf_releasebuffer*/ (releasebufferproc)0, + /*bf_getbuffer*/ SbkQByteArray_getbufferproc, + /*bf_releasebuffer*/ nullptr, }; -} +} // extern "C" // @snippet qbytearray-bufferprotocol // @snippet qbytearray-operatorplus-1 @@ -1049,8 +1055,8 @@ if (msec == 0) { // %FUNCTION_NAME() - disable generation of c++ function call Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); auto *timerType = Shiboken::SbkType(); - auto newFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_new)); - auto initFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_init)); + auto newFunc = reinterpret_cast(PyType_GetSlot(timerType, Py_tp_new)); + auto initFunc = reinterpret_cast(PyType_GetSlot(timerType, Py_tp_init)); auto *pyTimer = newFunc(Shiboken::SbkType(), emptyTuple, nullptr); initFunc(pyTimer, emptyTuple, nullptr); @@ -1076,6 +1082,8 @@ if (msec == 0) { Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(0)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); + if (Shiboken::Errors::occurred()) + Shiboken::Errors::storeErrorOrPrint(); Py_DECREF(callable); }; @@ -1084,8 +1092,8 @@ if (msec == 0) { } else { Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); auto *timerType = Shiboken::SbkType(); - auto newFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_new)); - auto initFunc = reinterpret_cast(PepType_GetSlot(timerType, Py_tp_init)); + auto newFunc = reinterpret_cast(PyType_GetSlot(timerType, Py_tp_new)); + auto initFunc = reinterpret_cast(PyType_GetSlot(timerType, Py_tp_init)); auto *pyTimer = newFunc(Shiboken::SbkType(), emptyTuple, nullptr); initFunc(pyTimer, emptyTuple, nullptr); @@ -1800,6 +1808,8 @@ auto cppCallback = [callable]() Shiboken::GilState state; Shiboken::AutoDecRef arglist(PyTuple_New(0)); Shiboken::AutoDecRef ret(PyObject_CallObject(callable, arglist)); + if (Shiboken::Errors::occurred()) + Shiboken::Errors::storeErrorOrPrint(); Py_DECREF(callable); }; // @snippet std-function-void-lambda @@ -2109,16 +2119,13 @@ if (!PyCallable_Check(callable)) { // object int count = 0; PyObject* fc = nullptr; -bool classMethod = false; -Shiboken::AutoDecRef func_ob(PyObject_GetAttr(callable, Shiboken::PyMagicName::func())); - -if (func_ob.isNull() && PyObject_HasAttr(callable, Shiboken::PyMagicName::code())) { +const bool classMethod = PyObject_HasAttr(callable, Shiboken::PyMagicName::func()); +if (classMethod) { + Shiboken::AutoDecRef func_ob(PyObject_GetAttr(callable, Shiboken::PyMagicName::func())); + fc = PyObject_GetAttr(func_ob, Shiboken::PyMagicName::code()); +} else if (PyObject_HasAttr(callable, Shiboken::PyMagicName::code())) { // variable `callable` is a function fc = PyObject_GetAttr(callable, Shiboken::PyMagicName::code()); -} else { - // variable `callable` is a class method - fc = PyObject_GetAttr(func_ob, Shiboken::PyMagicName::code()); - classMethod = true; } if (fc) { @@ -2148,6 +2155,8 @@ auto callback = [callable, arg_qpermission](const QPermission &permission) -> vo } else { Shiboken::AutoDecRef ret(PyObject_CallObject(callable, nullptr)); } + if (Shiboken::Errors::occurred()) + Shiboken::Errors::storeErrorOrPrint(); Py_DECREF(callable); }; Py_INCREF(callable); @@ -2504,3 +2513,19 @@ if (!vList.isEmpty() && std::all_of(vList.cbegin(), vList.cend(), isVariantList %0 = new %TYPE(vList, %2); } // @snippet qrangemodel-sequence-constructor + +// Enables populating QML ListView via properties of type "QList" +// @snippet register-qjsonobject-list +qRegisterMetaType>(); +// @snippet register-qjsonobject-list + +// Besides being a convenience function, this function also ensures a SbkConverter +// for "QList" is created. +// @snippet qjsonarray-toobjectlist +QList result; +const auto size = %CPPSELF.size(); +result.reserve(size); +for (qsizetype i = 0; i < size; ++i) + result.append(%CPPSELF.at(i).toObject()); +%PYARG_0 = %CONVERTTOPYTHON[QList](result); +// @snippet qjsonarray-toobjectlist diff --git a/sources/pyside6/PySide6/glue/qtdatavisualization.cpp b/sources/pyside6/PySide6/glue/qtdatavisualization.cpp index 3a179cb1..c8a972bd 100644 --- a/sources/pyside6/PySide6/glue/qtdatavisualization.cpp +++ b/sources/pyside6/PySide6/glue/qtdatavisualization.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet releaseownership Shiboken::Object::releaseOwnership(%PYARG_1); diff --git a/sources/pyside6/PySide6/glue/qtdesigner.cpp b/sources/pyside6/PySide6/glue/qtdesigner.cpp index d26d9f85..f50c8f9e 100644 --- a/sources/pyside6/PySide6/glue/qtdesigner.cpp +++ b/sources/pyside6/PySide6/glue/qtdesigner.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qtdesigner-registercustomwidget if (!QPyDesignerCustomWidgetCollection::_registerCustomWidgetHelper(%PYARG_1, kwds)) diff --git a/sources/pyside6/PySide6/glue/qtgraphs.cpp b/sources/pyside6/PySide6/glue/qtgraphs.cpp index b5a5db79..9eb9d8fa 100644 --- a/sources/pyside6/PySide6/glue/qtgraphs.cpp +++ b/sources/pyside6/PySide6/glue/qtgraphs.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet graphs-qsurfacedataproxy-resetarraynp auto data = QtGraphsHelper::surfaceDataFromNp(%1, %2, %3, %4, %5); diff --git a/sources/pyside6/PySide6/glue/qtgui.cpp b/sources/pyside6/PySide6/glue/qtgui.cpp index 4b8e718a..040c29ca 100644 --- a/sources/pyside6/PySide6/glue/qtgui.cpp +++ b/sources/pyside6/PySide6/glue/qtgui.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE @@ -790,10 +791,9 @@ if (_i) { // @snippet qstandarditemmodel-clear Shiboken::BindingManager &bm = Shiboken::BindingManager::instance(); -SbkObject *pyRoot = bm.retrieveWrapper(%CPPSELF.invisibleRootItem()); -if (pyRoot) { - Shiboken::Object::destroy(pyRoot, %CPPSELF.invisibleRootItem()); -} +// PYSIDE-56, invalidate root item +if (SbkObject *pyRoot = bm.retrieveWrapper(%CPPSELF.invisibleRootItem())) + Shiboken::Object::destroy(pyRoot, %CPPSELF.invisibleRootItem()); for (int r=0, r_max = %CPPSELF.rowCount(); r < r_max; r++) { QList ri = %CPPSELF.takeRow(0); diff --git a/sources/pyside6/PySide6/glue/qtmultimedia.cpp b/sources/pyside6/PySide6/glue/qtmultimedia.cpp index 45762163..bb67ffcf 100644 --- a/sources/pyside6/PySide6/glue/qtmultimedia.cpp +++ b/sources/pyside6/PySide6/glue/qtmultimedia.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qvideoframe-bits #include "object.h" diff --git a/sources/pyside6/PySide6/glue/qtnetwork.cpp b/sources/pyside6/PySide6/glue/qtnetwork.cpp index 9a4e28ce..f49089d7 100644 --- a/sources/pyside6/PySide6/glue/qtnetwork.cpp +++ b/sources/pyside6/PySide6/glue/qtnetwork.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qudpsocket-readdatagram Shiboken::ArrayPointer data(%ARGUMENT_NAMES); diff --git a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp index aa28f500..77c54977 100644 --- a/sources/pyside6/PySide6/glue/qtnetworkauth.cpp +++ b/sources/pyside6/PySide6/glue/qtnetworkauth.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qabstractoauth-lookuphost-functor struct QAbstractOAuthModifyFunctor : public Shiboken::PyObjectHolder diff --git a/sources/pyside6/PySide6/glue/qtopengl.cpp b/sources/pyside6/PySide6/glue/qtopengl.cpp index cdfaafb3..efdca212 100644 --- a/sources/pyside6/PySide6/glue/qtopengl.cpp +++ b/sources/pyside6/PySide6/glue/qtopengl.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qtpositioning.cpp b/sources/pyside6/PySide6/glue/qtpositioning.cpp index 91c331c7..f29b0b0a 100644 --- a/sources/pyside6/PySide6/glue/qtpositioning.cpp +++ b/sources/pyside6/PySide6/glue/qtpositioning.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qtprintsupport.cpp b/sources/pyside6/PySide6/glue/qtprintsupport.cpp index 39672c62..f9d4db71 100644 --- a/sources/pyside6/PySide6/glue/qtprintsupport.cpp +++ b/sources/pyside6/PySide6/glue/qtprintsupport.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet setpagesize bool out = %CPPSELF.setPageSize(%1); diff --git a/sources/pyside6/PySide6/glue/qtqml.cpp b/sources/pyside6/PySide6/glue/qtqml.cpp index a56db8de..82b16b8a 100644 --- a/sources/pyside6/PySide6/glue/qtqml.cpp +++ b/sources/pyside6/PySide6/glue/qtqml.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qmlerrror-repr const QByteArray message = %CPPSELF.toString().toUtf8(); @@ -76,6 +77,24 @@ if (instance.isNull()) { } // @snippet qqmlengine-singletoninstance-qmltypeid +// @snippet qqmlcomponent-createwithinitialpropertieswithownership +// Like createWithInitialProperties() but with Python ownership on the returned object. +PyThreadState *_save = PyEval_SaveThread(); // Py_BEGIN_ALLOW_THREADS +QObject *%0 = %CPPSELF.createWithInitialProperties(%1); +PyEval_RestoreThread(_save); // Py_END_ALLOW_THREADS +%PYARG_0 = %CONVERTTOPYTHON[QObject*](%0); +// @snippet qqmlcomponent-createwithinitialpropertieswithownership + +// @snippet qqmlcomponent-createwithownership +// Create a QML component instance and transfer ownership to Python, +// so no manual keep-alive bookkeeping is needed. +// This is specifically created and useful for QtBridges +PyThreadState *_save = PyEval_SaveThread(); // Py_BEGIN_ALLOW_THREADS +QObject *%0 = %CPPSELF.create(%1); +PyEval_RestoreThread(_save); // Py_END_ALLOW_THREADS +%PYARG_0 = %CONVERTTOPYTHON[QObject*](%0); +// @snippet qqmlcomponent-createwithownership + // @snippet qqmlengine-singletoninstance-typename QJSValue instance = %CPPSELF.singletonInstance(%1, %2); if (instance.isNull()) { diff --git a/sources/pyside6/PySide6/glue/qtquick.cpp b/sources/pyside6/PySide6/glue/qtquick.cpp index 8691af07..d63201da 100644 --- a/sources/pyside6/PySide6/glue/qtquick.cpp +++ b/sources/pyside6/PySide6/glue/qtquick.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qtquick PySide::initQuickSupport(module); diff --git a/sources/pyside6/PySide6/glue/qtquick3d.cpp b/sources/pyside6/PySide6/glue/qtquick3d.cpp index eee28645..199ba6ec 100644 --- a/sources/pyside6/PySide6/glue/qtquick3d.cpp +++ b/sources/pyside6/PySide6/glue/qtquick3d.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qtquicktest.cpp b/sources/pyside6/PySide6/glue/qtquicktest.cpp index f41735dd..6b2929d4 100644 --- a/sources/pyside6/PySide6/glue/qtquicktest.cpp +++ b/sources/pyside6/PySide6/glue/qtquicktest.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qtremoteobjects.cpp b/sources/pyside6/PySide6/glue/qtremoteobjects.cpp index 88d58589..3da71139 100644 --- a/sources/pyside6/PySide6/glue/qtremoteobjects.cpp +++ b/sources/pyside6/PySide6/glue/qtremoteobjects.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2024 Ford Motor Company // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qtro-init PySide::RemoteObjects::init(module); diff --git a/sources/pyside6/PySide6/glue/qtserialbus.cpp b/sources/pyside6/PySide6/glue/qtserialbus.cpp index 0bf918fd..b48651c9 100644 --- a/sources/pyside6/PySide6/glue/qtserialbus.cpp +++ b/sources/pyside6/PySide6/glue/qtserialbus.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qtsql.cpp b/sources/pyside6/PySide6/glue/qtsql.cpp index ff0d7a7a..04da1840 100644 --- a/sources/pyside6/PySide6/glue/qtsql.cpp +++ b/sources/pyside6/PySide6/glue/qtsql.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet simple-exec diff --git a/sources/pyside6/PySide6/glue/qtstatemachine.cpp b/sources/pyside6/PySide6/glue/qtstatemachine.cpp index 66c4acf1..6428b18d 100644 --- a/sources/pyside6/PySide6/glue/qtstatemachine.cpp +++ b/sources/pyside6/PySide6/glue/qtstatemachine.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qttest.cpp b/sources/pyside6/PySide6/glue/qttest.cpp index b7140828..4a8cf6b0 100644 --- a/sources/pyside6/PySide6/glue/qttest.cpp +++ b/sources/pyside6/PySide6/glue/qttest.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE diff --git a/sources/pyside6/PySide6/glue/qtuitools.cpp b/sources/pyside6/PySide6/glue/qtuitools.cpp index 6eba9a94..8865fa21 100644 --- a/sources/pyside6/PySide6/glue/qtuitools.cpp +++ b/sources/pyside6/PySide6/glue/qtuitools.cpp @@ -1,5 +1,7 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:critical reason:execute-external-code + // @snippet uitools-loadui /* * Based on code provided by: diff --git a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp index 42ac6e76..5b1b3d75 100644 --- a/sources/pyside6/PySide6/glue/qtwebenginecore.cpp +++ b/sources/pyside6/PySide6/glue/qtwebenginecore.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qwebenginecookiestore-functor struct QWebEngineCookieFilterFunctor : public Shiboken::PyObjectHolder diff --git a/sources/pyside6/PySide6/glue/qtwebview.cpp b/sources/pyside6/PySide6/glue/qtwebview.cpp new file mode 100644 index 00000000..e8eae4e5 --- /dev/null +++ b/sources/pyside6/PySide6/glue/qtwebview.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2026 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default + +// @snippet qwebview-runjavascriptfunctor +struct RunJavascriptFunctor : public Shiboken::PyObjectHolder +{ + using Shiboken::PyObjectHolder::PyObjectHolder; + + void operator()(const QVariant &result) + { + Shiboken::GilState state; + Shiboken::AutoDecRef arglist(PyTuple_Pack(1, PySide::Variant::javascriptVariantToPython(result))); + Shiboken::AutoDecRef ret(PyObject_CallObject(object(), arglist)); + release(); // single shot + } +}; +// @snippet qwebview-runjavascriptfunctor + +// @snippet qwebview-runjavascript +using RunJavascriptCallback = std::function; + +if (%PYARG_2 != nullptr && %PYARG_2 != Py_None) { + %CPPSELF.%FUNCTION_NAME(%1, RunJavascriptCallback(RunJavascriptFunctor(%PYARG_2))); +} else { + %CPPSELF.%FUNCTION_NAME(%1, RunJavascriptCallback{}); +} +// @snippet qwebview-runjavascript diff --git a/sources/pyside6/PySide6/glue/qtwidgets.cpp b/sources/pyside6/PySide6/glue/qtwidgets.cpp index c085cb06..7ea85112 100644 --- a/sources/pyside6/PySide6/glue/qtwidgets.cpp +++ b/sources/pyside6/PySide6/glue/qtwidgets.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default /********************************************************************* * INJECT CODE @@ -615,26 +616,6 @@ for (int i = 0, count = %CPPSELF.count(); i < count; ++i) { %CPPSELF.addAction(%1); // @snippet qlineedit-addaction -// addAction(QIcon,QString,const QObject*,const char*,Qt::ConnectionType) -// @snippet qwidget-addaction-1 -QAction *action = %CPPSELF.addAction(%1, %2); -%PYARG_0 = %CONVERTTOPYTHON[QAction *](action); -Shiboken::AutoDecRef result(PyObject_CallMethod(%PYARG_0, - "connect", "OsO", - %PYARG_0, SIGNAL(triggered()), %PYARG_3) -); -// @snippet qwidget-addaction-1 - -// addAction(QString,const QObject*,const char*,Qt::ConnectionType) -// @snippet qwidget-addaction-2 -QAction *action = %CPPSELF.addAction(%1); -%PYARG_0 = %CONVERTTOPYTHON[QAction *](action); -Shiboken::AutoDecRef result(PyObject_CallMethod(%PYARG_0, - "connect", "OsO", - %PYARG_0, SIGNAL(triggered()), %PYARG_2) -); -// @snippet qwidget-addaction-2 - // @snippet qtoolbar-clear QList lst; Shiboken::BindingManager &bm = Shiboken::BindingManager::instance(); diff --git a/sources/pyside6/PySide6/glue/qtxml.cpp b/sources/pyside6/PySide6/glue/qtxml.cpp index b97d774e..b9e48252 100644 --- a/sources/pyside6/PySide6/glue/qtxml.cpp +++ b/sources/pyside6/PySide6/glue/qtxml.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default // @snippet qdomdocument-setcontent QString _errorMsg_; diff --git a/sources/pyside6/PySide6/pyside6_global.h b/sources/pyside6/PySide6/pyside6_global.h index fe417b60..0838f02c 100644 --- a/sources/pyside6/PySide6/pyside6_global.h +++ b/sources/pyside6/PySide6/pyside6_global.h @@ -1,5 +1,6 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include diff --git a/sources/pyside6/PySide6/pysideqtesttouch.h b/sources/pyside6/PySide6/pysideqtesttouch.h index 4b4a4a9b..e24d3885 100644 --- a/sources/pyside6/PySide6/pysideqtesttouch.h +++ b/sources/pyside6/PySide6/pysideqtesttouch.h @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef PYSIDEQTESTTOUCH_H #define PYSIDEQTESTTOUCH_H diff --git a/sources/pyside6/PySide6/pysidewtypes.h b/sources/pyside6/PySide6/pysidewtypes.h index f853949a..7da42bb2 100644 --- a/sources/pyside6/PySide6/pysidewtypes.h +++ b/sources/pyside6/PySide6/pysidewtypes.h @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef __PYSIDEWTYPES__ #define __PYSIDEWTYPES__ diff --git a/sources/pyside6/PySide6/qiopipe.h b/sources/pyside6/PySide6/qiopipe.h index 6a325f18..f4d5051e 100644 --- a/sources/pyside6/PySide6/qiopipe.h +++ b/sources/pyside6/PySide6/qiopipe.h @@ -1,5 +1,6 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QIOPIPE_H #define QIOPIPE_H diff --git a/sources/pyside6/PySide6/qpydesignerextensions.h b/sources/pyside6/PySide6/qpydesignerextensions.h index 871fa828..c4477031 100644 --- a/sources/pyside6/PySide6/qpydesignerextensions.h +++ b/sources/pyside6/PySide6/qpydesignerextensions.h @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QPYDESIGNEREXTENSIONS_H #define QPYDESIGNEREXTENSIONS_H diff --git a/sources/pyside6/PySide6/qpyqmlparserstatus.h b/sources/pyside6/PySide6/qpyqmlparserstatus.h index 24dcdda7..687de1d6 100644 --- a/sources/pyside6/PySide6/qpyqmlparserstatus.h +++ b/sources/pyside6/PySide6/qpyqmlparserstatus.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QPYQMLPARSERSTATUS_H #define QPYQMLPARSERSTATUS_H diff --git a/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h b/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h index 0b6a0b95..7b2be872 100644 --- a/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h +++ b/sources/pyside6/PySide6/qpyqmlpropertyvaluesource.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QPYQMLPROPERTYVALUESOURCE_H #define QPYQMLPROPERTYVALUESOURCE_H diff --git a/sources/pyside6/PySide6/qpytextobject.cpp b/sources/pyside6/PySide6/qpytextobject.cpp index 501db2af..3abb388f 100644 --- a/sources/pyside6/PySide6/qpytextobject.cpp +++ b/sources/pyside6/PySide6/qpytextobject.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #include "qpytextobject.h" diff --git a/sources/pyside6/PySide6/qpytextobject.h b/sources/pyside6/PySide6/qpytextobject.h index 730cc441..6599b949 100644 --- a/sources/pyside6/PySide6/qpytextobject.h +++ b/sources/pyside6/PySide6/qpytextobject.h @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QPYTEXTOBJECT #define QPYTEXTOBJECT diff --git a/sources/pyside6/PySide6/qtcorehelper.h b/sources/pyside6/PySide6/qtcorehelper.h index 6bce2f5f..bbbb81fc 100644 --- a/sources/pyside6/PySide6/qtcorehelper.h +++ b/sources/pyside6/PySide6/qtcorehelper.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QTCOREHELPER_H #define QTCOREHELPER_H diff --git a/sources/pyside6/PySide6/qtdatavisualization_helper.h b/sources/pyside6/PySide6/qtdatavisualization_helper.h index 8fee4492..49faf399 100644 --- a/sources/pyside6/PySide6/qtdatavisualization_helper.h +++ b/sources/pyside6/PySide6/qtdatavisualization_helper.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QTDATAVISUALIZATION_HELPER_H #define QTDATAVISUALIZATION_HELPER_H diff --git a/sources/pyside6/PySide6/qtdbushelper.h b/sources/pyside6/PySide6/qtdbushelper.h index 7389ad81..b9659ca2 100644 --- a/sources/pyside6/PySide6/qtdbushelper.h +++ b/sources/pyside6/PySide6/qtdbushelper.h @@ -1,5 +1,6 @@ // Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QTDBUSHELPER_H #define QTDBUSHELPER_H diff --git a/sources/pyside6/PySide6/qtgraphs_helper.h b/sources/pyside6/PySide6/qtgraphs_helper.h index e488fc7d..079f9e0e 100644 --- a/sources/pyside6/PySide6/qtgraphs_helper.h +++ b/sources/pyside6/PySide6/qtgraphs_helper.h @@ -1,5 +1,6 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QTGRAPHS_HELPER_H #define QTGRAPHS_HELPER_H diff --git a/sources/pyside6/PySide6/qtguihelper.h b/sources/pyside6/PySide6/qtguihelper.h index e537cfef..637ff0a2 100644 --- a/sources/pyside6/PySide6/qtguihelper.h +++ b/sources/pyside6/PySide6/qtguihelper.h @@ -1,5 +1,6 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +// Qt-Security score:significant reason:default #ifndef QTGUIHELPER_H #define QTGUIHELPER_H diff --git a/sources/pyside6/PySide6/templates/common.xml b/sources/pyside6/PySide6/templates/common.xml index 663a0a5c..9913d768 100644 --- a/sources/pyside6/PySide6/templates/common.xml +++ b/sources/pyside6/PySide6/templates/common.xml @@ -2,6 +2,7 @@